Description

Use historical draw results, and number of hunters to train a model we can use to predict the number of hunters in future years.

TODO - Include other potential inputs that could impact how many hunters get a license and show up. Those could include economic indicators, and costs associated with hunting (transportation, lodging).

NOTICE that I am only looking at the general rifle hunting seasons on public land. There are also hunters for Archery, Muzzleloader, Private Land, Ranching for Wildlife, etc.


Setup

Load required libraries for wrangling data, charting, and mapping

library(plyr,quietly = T) # data wrangling
library(dplyr,quietly = T) # data wrangling
library(ggplot2, quietly = T) # charting
library(ggthemes,quietly = T) # so I can add the highcharts theme and palette
library(scales,quietly = T) # to load the percent function when labeling plots
library(caret,quietly = T) # classification and regression training
library(foreach,quietly = T) # parallel processing to speed up the model training
library(doMC,quietly = T) # parallel processing to speed up the model training
library(lubridate,quietly = T) # for timing models

Set our preferred charting theme

theme_set(theme_minimal()+theme_hc()+theme(legend.key.width = unit(1.5, "cm")))

Run script to get hunter data

source('~/_code/colorado-dow/datasets/Colorado Elk Harvest Data.R', echo=F)

Table of the harvest data COElkRifleAll

Run script to get draw data

source('~/_code/colorado-dow/datasets/Elk Drawing Summaries.R', echo=F)

Table of the data

head(COElkDrawAll)

source geodata

source('~/_code/colorado-dow/datasets/Colorado GMUnit and Road data.R', echo=F)

Take a peak at the boundary data

head(Unitboundaries2)

Set to predictive analytics directory


Organize data

Will start by grouping all of the seasons together, and modeling the number of hunters per Year and Unit Group Draw results data by Year and Unit

COElkDraw <- summarise(group_by(COElkDrawAll,Year,Unit),
                       Quota = sum(Orig_Quota,na.rm = T),
                       Drawn = sum(Chcs_Drawn,na.rm = T))

Appropriate field classes for model training

COElkDraw$Year <- as.numeric(COElkDraw$Year)

Group Hunter data by Year and Unit

COElkHunters <- summarise(group_by(COElkRifleAll,Year,Unit),
                          Hunters = sum(c(Hunters.Antlered,Hunters.Antlerless,Hunters.Either),na.rm = T))

COElkHunters$Year <- as.numeric(COElkHunters$Year)

Join in Hunter and Draw data together

COElkHunters <- left_join(COElkHunters, COElkDraw, by = c("Year","Unit"))

Replace the draw data that don’t have entries with 0

COElkHunters$Drawn[is.na(COElkHunters$Drawn)] <- 0
COElkHunters$Quota[is.na(COElkHunters$Quota)] <- 0

Split into train and test sets. Will use 75% of the data to train on. Be sure to include each unit in the split. … so do the split for each unit, first make sure each Unit has at least three entries

COElkHunters <- mutate(group_by(COElkHunters,Unit),
                       numentries = n())
COElkHunters <- filter(COElkHunters, numentries >= 3)
COElkHunters$UnitYear <- paste(COElkHunters$Unit, COElkHunters$Year)

traindata <- COElkHunters %>% group_by(Unit) %>% sample_frac(size = .75, replace = F)
testdata <- COElkHunters[!COElkHunters$UnitYear %in% traindata$UnitYear,]

COElkHunters <- select(COElkHunters, -UnitYear, -numentries)

traindata <- select(traindata, -UnitYear, -numentries)
testdata <- select(testdata, -UnitYear, -numentries)

Save off for importing into AzureML

write.csv(COElkHunters,file = "~/_code/colorado-dow/datasets/COElkHunters.csv",row.names = F)

notice that the number of hunters data is skewed.

ggplot(COElkHunters, aes(Hunters)) + 
  geom_density() +
  xlab("Hunters in Unit") +
  ylab("Number of Units") +
  theme(axis.text.y = element_blank()) +
  labs(title="Distribution of Hunters in each Unit", subtitle="2006-2017", caption="source: cpw.state.co.us")

A general rule of thumb to consider is that skewed data whose ratio of the highest value to the lowest value is greater than 20 have significant skewness. Also, the skewness statistic can be used as a diagnostic. If the predictor distribution is roughly symmetric, the skewness values will be close to zero. As the distribution becomes more right skewed, the skewness statistic becomes larger. Similarly, as the distribution becomes more left skewed, the value becomes negative. Replacing the data with the log, square root, or inverse may help to remove the skew.

Example of how BoxCox can redistribute the data

preProcValues2 <- preProcess(as.data.frame(traindata), method = "BoxCox")
trainBC <- predict(preProcValues2, as.data.frame(traindata))
ggplot(trainBC, aes(Hunters)) + 
  geom_density() +
  xlab("BoxCox Hunters in Unit") +
  ylab("Number of Units") +
  theme(axis.text.y = element_blank()) +
  labs(title="BoxCox Distribution of Hunters in each Unit", subtitle="2006-2017", caption="source: cpw.state.co.us")

caret has a preproccess function for correcting for skewness ‘BoxCox’, we will need to be sure to look at using this function in the training models.

Model Building

This is quite an iterative process. It is important to document and save off data. Run thru differing ‘quick to train’ methods Which one performed the best? Determine other similar methods and run them. Which one performed the best? Determine disimmilar methods. Which one performed the best?

Now lets work on some refined tuning. Assess preprocessing functions. center, scale, pca, boxcox, nzv, etc some units have very little data, should we remove them? ’zero variances 123, 791, 87, 94, 88 TODO, frequency plot of Unit

Run the list of other favorable methods with preprocessing in place Further tune method’s parameters

Is there a method that is best? Are some methods better at part of the data than others? maybe we combine?

Insert into AzureML to further inspections and create into a webservice If the packages or methods are not yet supported in Azure, we will need to create an R Model instead of just running an rscript.

Additonally, AzureML has some additional methods to consider, ensure we attempt to use those as well.

Step 1 - Loop through possible methods, utilizing the quicker ‘adaptive_cv’ parameter search from caret. # Consider scripting this into AzureML to make it run much faster, though there is more setup and errors to control for

quickmethods <- c("lm",'svmLinear',"svmRadial","knn","cubist","kknn","glm.nb")

step1_all <- NULL
for (imethod in quickmethods) {
  step1 <- NULL
  start <- now()
  
  if (imethod == "lm") {
    controlmethod <- "repeatedcv"
  } else {controlmethod <- "adaptive_cv"}
  
  fitControl <- trainControl(
    method = controlmethod,
    # search = 'random',
    number = 4,
    repeats = 4,
    allowParallel = TRUE,
    summaryFunction = defaultSummary)
  
  registerDoSEQ()
  registerDoMC(cores = 6)
  
  HuntersModel_1 = train(Hunters ~ ., data = traindata,
                         method = imethod,
                         preProc = c("center","scale"), 
                         tuneLength = 15,
                         trControl = fitControl)
  
  HuntersModel_1
  
  # measure performance
  predictdata <- predict(HuntersModel_1, testdata)
  
  step1$method <- imethod
  step1$RMSE <- postResample(pred = predictdata, obs = testdata$Hunters)[1]
  step1$duration <- now() - start
  step1 <- as.data.frame(step1)
  step1_all <- rbind(step1_all,step1)
}

View Results

step1_all
top_two_models <- top_n(step1_all,2,-RMSE)$method

Take the top two and determine some additonal methods to try by maximizing the Jaccard dissimilarity between sets of models

tag <- read.csv("tag_data.csv", row.names = 1)
tag <- as.matrix(tag)

Select only models for regression

regModels <- tag[tag[,"Regression"] == 1,]

all <- 1:nrow(regModels)
dissimilarmethods_all <- NULL
for (itoptwo in 1:2) {
  ## Seed the analysis with the model of interest
  start <- grep(top_two_models[itoptwo], rownames(regModels), fixed = TRUE)
  pool <- all[all != start]
  
  ## Select 4 model models by maximizing the Jaccard
  ## dissimilarity between sets of models
  nextMods <- maxDissim(regModels[start,,drop = FALSE], 
                        regModels[pool, ], 
                        method = "Jaccard",
                        n = 4)
  
  rownames(regModels)[c(nextMods)]
  
  dissimilarmethods <- rownames(regModels)[nextMods]
  dissimilarmethods <- str_extract(string = dissimilarmethods,pattern = "[:alnum:]+(?=\\))")
  dissimilarmethods_all <- c(dissimilarmethods_all,dissimilarmethods)
}

Now we have 8 more methods to try in the same manner

dissimilarmethods_all <- unique(dissimilarmethods_all)
for (imethod in dissimilarmethods_all) {
  step1 <- NULL
  start_timer <- now()[1]
  
  if (imethod == "lm") {
    controlmethod <- "repeatedcv"
  } else {controlmethod <- "adaptive_cv"}
  
  fitControl <- trainControl(
    method = controlmethod,
    # search = 'random',
    number = 4,
    repeats = 4,
    allowParallel = TRUE,
    summaryFunction = defaultSummary)
  
  registerDoSEQ()
  registerDoMC(cores = 6)
  
  HuntersModel_1 = train(Hunters ~ ., data = traindata,
                         method = imethod,
                         preProc = c("center","scale"), 
                         tuneLength = 15,
                         trControl = fitControl)
  
  HuntersModel_1
  
  # measure performance
  predictdata <- predict(HuntersModel_1, testdata)
  
  step1$method <- imethod
  step1$RMSE <- postResample(pred = predictdata, obs = testdata$Hunters)[1]
  step1$duration <- now()[1] - start_timer[1]
  step1 <- as.data.frame(step1)
  step1_all <- rbind(step1_all,step1)
}
step1_all
         method     RMSE       duration
RMSE         lm 165.0025  6.866598 secs
RMSE1 svmLinear 178.0942  4.950974 secs
RMSE2 svmRadial 134.0561 22.081016 secs
RMSE3       knn 733.8210 10.502875 secs
RMSE4    cubist 151.9572  3.057477 secs
RMSE5      kknn 132.0211  9.778734 secs
RMSE6    cubist 160.5464  1.027274 secs

Now lets work on some refined tuning on the top methods Any valuable preprocessing steps?

preprocessfunctions <- c("BoxCox", "YeoJohnson", "expoTrans", "center", "scale", "range", "knnImpute", "bagImpute", "medianImpute", "pca", "ica", "spatialSign", "corr", "zv", "nzv")
topmethods <- top_n(step1_all,2,-RMSE)$method

fitControl <- trainControl(
  method = "adaptive_cv", #repeatedcv, 
  search = 'random',
  number = 10, #4
  repeats = 10, #10
  # classProbs = TRUE,
  # savePred = TRUE,
  allowParallel = TRUE,
  summaryFunction = defaultSummary)

PPperformance_all <- NULL
PPperformance <- NULL
for (imethod in topmethods) {
  for (ipreprocess in preprocessfunctions) {
    registerDoSEQ()
    registerDoMC(cores = 6)
    
    PreProcessModel = train(Hunters ~ ., data = traindata,
                         method = imethod,
                         preProc = ipreprocess, 
                         #tuneLength = 10,
                         #tuneGrid = kknnTuneGrid,
                         trControl = fitControl)
    
    print(PreProcessModel)
    
    # check performance
    predictdata <- predict(PreProcessModel, testdata)
    
    PPperformance$method <- imethod
    PPperformance$preprocess <- ipreprocess
    PPperformance$RMSE <- postResample(pred = predictdata, obs = testdata$Hunters)[1]
    PPperformance <- as.data.frame(PPperformance)
    PPperformance_all <- rbind(PPperformance_all,PPperformance)
  }
}
PPperformance_all
Error: object 'PPperformance_all' not found

Output from AzureML [ModuleOutput] method preprocess RMSE [ModuleOutput] RMSE kknn BoxCox 130.7939 [ModuleOutput] RMSE1 kknn YeoJohnson 130.9600 [ModuleOutput] RMSE2 kknn center 130.7331 [ModuleOutput] RMSE3 kknn scale 130.1818 [ModuleOutput] RMSE4 kknn pca 130.2071 [ModuleOutput] RMSE5 svmRadial BoxCox 154.0898 [ModuleOutput] RMSE6 svmRadial YeoJohnson 169.9816 [ModuleOutput] RMSE7 svmRadial center 154.1891 [ModuleOutput] RMSE8 svmRadial scale 154.1000 [ModuleOutput] RMSE9 svmRadial pca 164.0881 svmRadial and kknn don’t perform better with any of the preprocessing functions in place

Now we can review the predictors, there are only a few fields so I will manually test performance while excluding each of them to monitor their importance. Some of our fields are instinctively required (Year, Unit)

Predictors <- c("Quota","Drawn")
Predictorperformance_all <- NULL
Predictorperformance <- NULL
for (imethod in topmethods) {
  for (ipredictor in Predictors) {
    registerDoSEQ()
    registerDoMC(cores = 6)
    
    PredictorModel = train(Hunters ~ ., data = select(traindata,-ipredictor),
                            method = imethod,
                            tuneLength = 15,
                            trControl = fitControl)
    
    print(PredictorModel)
    
    # check performance
    predictdata <- predict(PredictorModel, testdata)
    
    Predictorperformance$method <- imethod
    Predictorperformance$missing_predictor <- ipredictor
    Predictorperformance$RMSE <- postResample(pred = predictdata, obs = testdata$Hunters)[1]
    Predictorperformance <- as.data.frame(Predictorperformance)
    Predictorperformance_all <- rbind(Predictorperformance_all,Predictorperformance)
  }
}
Predictorperformance_all
         method missing_predictor     RMSE
RMSE  svmRadial             Quota 152.5027
RMSE1 svmRadial             Drawn 153.7358
RMSE2      kknn             Quota 131.1298
RMSE3      kknn             Drawn 134.1082
RMSE4      kknn   Quota and Drawn 130.3965

svMRadial will perform better with all of the predictors, while kknn performs better with only Unit and Year fields

Use above information to test out various combinations of preprocessing and predictor sets

kknn without Quota and Drawn

fitControl <- trainControl(
  method = "adaptive_cv", #repeatedcv, 
  search = 'random',
  number = 10, #4
  repeats = 10, #10
  # classProbs = TRUE,
  # savePred = TRUE,
  allowParallel = TRUE,
  summaryFunction = defaultSummary)

registerDoSEQ()
registerDoMC(cores = 6)

kknnModel = train(Hunters ~ ., data = select(COElkHunters,-Quota, -Drawn),
                  method = "kknn",
                  tuneLength = 75,
                  trControl = fitControl)
kknnModel
k-Nearest Neighbors 

1540 samples
   2 predictor

No pre-processing
Resampling: Adaptively Cross-Validated (10 fold, repeated 10 times) 
Summary of sample sizes: 1387, 1387, 1388, 1386, 1385, 1385, ... 
Resampling results across tuning parameters:

  kmax  distance   kernel        RMSE      Rsquared   MAE       Resamples
   20   2.2190668  epanechnikov  131.6756  0.9799921  83.10401    5      
   23   2.5546249  cos           131.4289  0.9800702  82.94570    5      
   26   1.2077079  triweight     130.1354  0.9802204  82.38677  100      
   35   1.4783952  inv           130.6411  0.9803881  82.92012    5      
   37   0.2302676  biweight      128.8218  0.9796468  82.61469    9      
   39   2.9238407  epanechnikov  131.6756  0.9799921  83.10401    5      
   40   1.9249761  epanechnikov  131.6756  0.9799921  83.10400    5      
   45   0.5708073  cos           131.4271  0.9800698  82.95231    5      
   46   1.5130392  rectangular   132.7737  0.9797456  84.93577    5      
   47   1.5066909  inv           130.6411  0.9803881  82.92012    5      
   51   1.6936498  triangular    131.0532  0.9801930  82.70696    5      
   59   0.4340342  inv           130.6411  0.9803881  82.92012    5      
   64   0.8707815  biweight      128.8075  0.9796519  82.54501    9      
   69   0.4283565  rectangular   132.7737  0.9797456  84.93577    5      
   72   0.1078861  gaussian      131.8750  0.9800032  84.17641    5      
   72   1.8257081  rectangular   132.7737  0.9797456  84.93577    5      
   94   1.5894162  triweight     128.1327  0.9814758  80.97408    6      
   95   2.2151232  epanechnikov  131.6756  0.9799921  83.10401    5      
  100   0.3817441  gaussian      131.8701  0.9800047  84.16123    5      
  108   1.2081158  cos           131.4288  0.9800702  82.94535    5      
  111   2.4803098  inv           130.6411  0.9803881  82.92012    5      
  118   0.5056196  biweight      128.8059  0.9796519  82.54077    9      
  122   1.0827835  biweight      128.4338  0.9814240  81.35076    6      
  126   0.9625002  triangular    131.0528  0.9801930  82.70613    5      
  130   2.8021649  triangular    131.0532  0.9801930  82.70698    5      
  151   1.2865492  triweight     130.1354  0.9802204  82.38677  100      
  155   0.3185608  triangular    131.0572  0.9801902  82.73030    5      
  158   2.9508586  rectangular   132.7737  0.9797456  84.93577    5      
  164   0.3406626  biweight      128.8112  0.9796498  82.56733    9      
  175   1.8822426  gaussian      131.8656  0.9800062  84.14435    5      
  180   0.7829322  inv           130.6411  0.9803881  82.92012    5      
  183   2.8578463  triweight     128.1327  0.9814758  80.97408    6      
  196   0.9738625  biweight      128.8076  0.9796519  82.54521    9      
  197   1.9103930  triangular    131.0532  0.9801930  82.70697    5      
  208   1.7948344  rectangular   132.7737  0.9797456  84.93577    5      
  209   2.8788390  epanechnikov  131.6756  0.9799921  83.10401    5      
  216   0.1616174  cos           131.4495  0.9800625  83.03523    5      
  219   1.5906034  triweight     128.1327  0.9814758  80.97408    6      
  224   2.5172216  triangular    131.0532  0.9801930  82.70698    5      
  226   2.4874363  inv           131.6418  0.9800365  82.90705    5      
  232   0.4289729  triangular    131.0522  0.9801921  82.71857    5      
  232   1.1267958  rectangular   132.7737  0.9797456  84.93577    5      
  239   0.2772930  biweight      128.4484  0.9814182  81.42926    6      
  244   0.7280230  triweight     128.1326  0.9814758  80.97385    6      
  247   0.3054103  gaussian      131.8722  0.9800041  84.16786    5      
  257   0.4641430  inv           130.6411  0.9803881  82.92012    5      
  313   1.5046697  triweight     130.1354  0.9802204  82.38677  100      
  317   1.6199505  triangular    131.0531  0.9801930  82.70695    5      
  319   1.6611979  triweight     130.1354  0.9802204  82.38677  100      
  323   2.2840918  cos           131.4289  0.9800702  82.94570    5      
  327   2.4589409  gaussian      131.8656  0.9800062  84.14434    5      
  338   0.5952266  inv           130.6411  0.9803881  82.92012    5      
  341   0.8479846  biweight      128.4336  0.9814240  81.35023    6      
  345   2.3194921  cos           131.4289  0.9800702  82.94570    5      
  348   1.6925820  cos           131.4289  0.9800702  82.94567    5      
  371   2.5390863  rectangular   132.7737  0.9797456  84.93577    5      
  377   0.1170220  gaussian      131.8750  0.9800032  84.17640    5      
  380   0.6688485  triweight     130.1354  0.9802204  82.38664  100      
  386   2.5533768  gaussian      131.8656  0.9800062  84.14434    5      
  390   0.7606599  inv           130.6411  0.9803881  82.92012    5      
  391   1.4796130  gaussian      131.8656  0.9800062  84.14437    5      
  396   2.4921411  triangular    131.0532  0.9801930  82.70698    5      
  402   1.9321351  cos           131.4289  0.9800702  82.94569    5      
  429   2.2906721  epanechnikov  131.6756  0.9799921  83.10401    5      
  432   2.4669464  epanechnikov  131.6756  0.9799921  83.10401    5      
  435   1.9112202  rectangular   132.7737  0.9797456  84.93577    5      
  460   1.0723682  cos           131.4286  0.9800702  82.94498    5      
  460   1.4085408  triweight     128.1327  0.9814758  80.97408    6      
  465   0.1073528  triweight     127.8562  0.9801797  82.07937   10      
  469   0.9612949  epanechnikov  131.6750  0.9799922  83.10237    5      
  476   0.6659399  triangular    131.0518  0.9801930  82.70557    5      
  477   1.1059912  gaussian      131.8657  0.9800062  84.14460    5      
  477   2.3521826  inv           130.6411  0.9803881  82.92012    5      
  500   0.7270984  rectangular   132.7737  0.9797456  84.93577    5      
  508   0.3723910  biweight      128.8089  0.9796506  82.55357    9      

RMSE was used to select the optimal model using the smallest value.
The final values used for the model were kmax = 380, distance = 0.6688485 and kernel = triweight.

Best RMSE, not sure why caret is selecting parameters with higher RMSE, lets select manually

RSMEkknn <- filter(kknnModel$results, RMSE == min(RMSE))
RSMEkknn$kernel <- as.character(RSMEkknn$kernel)

run again with a tune grid

kknnTuneGrid <- data.frame(kmax = c(RSMEkknn$kmax,RSMEkknn$kmax,RSMEkknn$kmax,RSMEkknn$kmax,RSMEkknn$kmax),
                           distance = c(RSMEkknn$distance*.7,RSMEkknn$distance*.9,RSMEkknn$distance,RSMEkknn$distance*1.1,RSMEkknn$distance*1.3),
                           kernel = c(RSMEkknn$kernel,RSMEkknn$kernel,RSMEkknn$kernel,RSMEkknn$kernel,RSMEkknn$kernel))

fitControl <- trainControl(
  method = "repeatedcv", #repeatedcv, 
  number = 10, #4
  repeats = 10, #10
  allowParallel = TRUE,
  summaryFunction = defaultSummary)

registerDoSEQ()
registerDoMC(cores = 6)

kknnGridModel = train(Hunters ~ ., data = select(COElkHunters,-Quota, -Drawn),
                  method = "kknn",
                  tuneGrid = kknnTuneGrid,
                  trControl = fitControl)
kknnGridModel
k-Nearest Neighbors 

1540 samples
   2 predictor

No pre-processing
Resampling: Cross-Validated (10 fold, repeated 10 times) 
Summary of sample sizes: 1386, 1388, 1388, 1384, 1388, 1385, ... 
Resampling results across tuning parameters:

  distance    RMSE      Rsquared   MAE     
  0.07514696  128.2060  0.9808475  81.25859
  0.09661752  128.2060  0.9808476  81.25851
  0.10735280  128.2060  0.9808476  81.25846
  0.11808808  128.2059  0.9808476  81.25838
  0.13955864  128.2058  0.9808476  81.25803

Tuning parameter 'kmax' was held constant at a value of 465
Tuning parameter 'kernel' was held constant at a value
 of triweight
RMSE was used to select the optimal model using the smallest value.
The final values used for the model were kmax = 465, distance = 0.1395586 and kernel = triweight.

Best RMSE, not sure why caret is selecting parameters with higher RMSE, lets select manually

RSMEkknn <- filter(kknnGridModel$results, RMSE == min(RMSE))
RSMEkknn$kernel <- as.character(RSMEkknn$kernel)

run again with a tune grid

kknnTuneGrid2 <- data.frame(kmax = c(RSMEkknn$kmax*.7,RSMEkknn$kmax*.9,RSMEkknn$kmax,RSMEkknn$kmax*1.1,RSMEkknn$kmax*1.3),
                           distance = c(RSMEkknn$distance,RSMEkknn$distance,RSMEkknn$distance,RSMEkknn$distance,RSMEkknn$distance),
                           kernel = c(RSMEkknn$kernel,RSMEkknn$kernel,RSMEkknn$kernel,RSMEkknn$kernel,RSMEkknn$kernel))

registerDoSEQ()
registerDoMC(cores = 6)

kknnGridModel2 = train(Hunters ~ ., data = select(COElkHunters,-Quota, -Drawn),
                      method = "kknn",
                      tuneGrid = kknnTuneGrid2,
                      trControl = fitControl)
kknnGridModel2
k-Nearest Neighbors 

1540 samples
   2 predictor

No pre-processing
Resampling: Cross-Validated (10 fold, repeated 10 times) 
Summary of sample sizes: 1387, 1385, 1386, 1386, 1386, 1387, ... 
Resampling results across tuning parameters:

  kmax   RMSE      Rsquared   MAE     
  325.5  129.3056  0.9804359  81.89613
  418.5  129.3056  0.9804359  81.89613
  465.0  129.3056  0.9804359  81.89613
  511.5  129.3056  0.9804359  81.89613
  604.5  129.3056  0.9804359  81.89613

Tuning parameter 'distance' was held constant at a value of 0.1395586
Tuning parameter 'kernel' was held constant at
 a value of triweight
RMSE was used to select the optimal model using the smallest value.
The final values used for the model were kmax = 604.5, distance = 0.1395586 and kernel = triweight.

One more time on final parameter (kernel) Best RMSE, not sure why caret is selecting parameters with higher RMSE, lets select manually

RSMEkknn <- filter(kknnGridModel2$results, RMSE == min(RMSE))[1,]
kernels <- levels(kknnModel$results$kernel)

run again with a tune grid

kknnTuneGrid3 <- data.frame(kmax = rep(465.0,8),
                            distance = rep(0.1395586,8),
                            kernel = kernels)

registerDoSEQ()
registerDoMC(cores = 6)

kknnGridModel3 = train(Hunters ~ ., data = select(COElkHunters,-Quota, -Drawn),
                       method = "kknn",
                       tuneGrid = kknnTuneGrid3,
                       trControl = fitControl)
kknnGridModel3
k-Nearest Neighbors 

1540 samples
   2 predictor

No pre-processing
Resampling: Cross-Validated (10 fold, repeated 10 times) 
Summary of sample sizes: 1385, 1387, 1386, 1386, 1385, 1385, ... 
Resampling results across tuning parameters:

  kernel        RMSE      Rsquared   MAE     
  biweight      131.7906  0.9797220  83.06975
  cos           132.5038  0.9795046  83.42612
  epanechnikov  132.3542  0.9795478  83.44426
  gaussian      132.2517  0.9795637  83.47951
  inv           131.4340  0.9798233  82.78442
  rectangular   133.2426  0.9792578  84.07603
  triangular    132.3594  0.9795341  83.35722
  triweight     130.7170  0.9800337  82.49768

Tuning parameter 'kmax' was held constant at a value of 465
Tuning parameter 'distance' was held constant at a value
 of 0.1395586
RMSE was used to select the optimal model using the smallest value.
The final values used for the model were kmax = 465, distance = 0.1395586 and kernel = triweight.
RSMEkknn <- filter(kknnGridModel3$results, RMSE == min(RMSE))

Best RMSE for kknn thus far

RSMEkknn <- filter(kknnModel$results, RMSE == min(RMSE))

Work thru some resampling methods with best kknn params

kknnTuneGrid4 <- data.frame(kmax = RSMEkknn$kmax,
                            distance = RSMEkknn$distance,
                            kernel = as.character(RSMEkknn$kernel))

trainmethods <- c("boot", "boot632", "optimism_boot", "boot_all", "cv", "repeatedcv", "LOOCV", "LGOCV", "none")
trainmethodperformance_all <- NULL
for (itrainmethod in trainmethods) {
  trainmethodperformance <- NULL
  fitControl <- trainControl(
    method = itrainmethod,
    number = 10, #4
    repeats = 10, #10
    allowParallel = TRUE,
    summaryFunction = defaultSummary)
  
  registerDoSEQ()
  registerDoMC(cores = 6)
  
  kknnTrainModel = train(Hunters ~ ., data = select(COElkHunters,-Quota, -Drawn),
                         method = "kknn",
                         tuneGrid = kknnTuneGrid4,
                         trControl = fitControl)
  
  print(kknnTrainModel)
  trainmethodperformance <- filter(kknnTrainModel$results, RMSE == min(RMSE))
  trainmethodperformance$trainmethod <- itrainmethod
  trainmethodperformance_all <- rbind.fill(trainmethodperformance_all,trainmethodperformance)
}
trainmethodperformance_all
      sigma    C     RMSE  Rsquared       MAE    RMSESD  RsquaredSD      MAESD   trainmethod RMSEApparent
1 0.0037653 2048 360.4005 0.7726028 231.54142 282.30458 0.298028249 191.167393          boot           NA
2 0.0037653 2048 193.7360 0.9278034 123.00804 200.64763 0.209256870 132.918016       boot632      94.5999
3 0.0037653 2048 131.0365 0.9805517  83.53679  19.64063 0.008462524   7.577739 optimism_boot      94.5999
4 0.0037653 2048 135.4261 0.9789121  92.45378  11.02942 0.003095162   5.487161            cv           NA
5 0.0037653 2048 141.6488 0.9759367  94.08007  32.98579 0.017374430   9.333094    repeatedcv           NA
6 0.0037653 2048 133.2932 0.9794390  90.18903        NA          NA         NA         LOOCV           NA
7 0.0037653 2048 150.4885 0.9732360  99.97503  13.06248 0.004776229   6.047319         LGOCV           NA
  RsquaredApparent MAEApparent RMSEOptimism RsquaredOptimism MAEOptimism
1               NA          NA           NA               NA          NA
2        0.9898111    69.14552           NA               NA          NA
3        0.9898111    69.14552     36.43665     -0.009259344    14.39127
4               NA          NA           NA               NA          NA
5               NA          NA           NA               NA          NA
6               NA          NA           NA               NA          NA
7               NA          NA           NA               NA          NA
fitControl <- trainControl(
  method = "optimism_boot",
  number = 10, #4
  allowParallel = TRUE,
  summaryFunction = defaultSummary)

kknnFinalTrainModel = train(Hunters ~ ., data = COElkHunters,
                       method = "kknn",
                       tuneGrid = kknnTuneGrid4,
                       trControl = fitControl)

save off for future loading

save(kknnFinalTrainModel, file = "~/_code/colorado-dow/datasets/kknnFinalTrainModel.RData")

back to train vs test data for one more performance measure and chart… even though for future data we will use the final trained model

kknnTrainModel = train(Hunters ~ ., data = traindata,
                            method = "kknn",
                            tuneGrid = kknnTuneGrid4,
                            trControl = fitControl)

check performance

predictdata <- predict(kknnTrainModel, testdata)

postResample(pred = predictdata, obs = testdata$Hunters)

Chart performance of predicted

chartperformance <- data.frame(predicted = predictdata, observed = testdata$Hunters)
ggplot(chartperformance, aes(predicted,observed)) +
  geom_point() +
  labs(title="Performance of Number of Hunters Prediction", caption="source: cpw.state.co.us")

SVM

Output from AzureML [ModuleOutput] Support Vector Machines with Radial Basis Function Kernel [ModuleOutput] [ModuleOutput] 1540 samples [ModuleOutput] 4 predictors [ModuleOutput] [ModuleOutput] No pre-processing [ModuleOutput] Resampling: Cross-Validated (10 fold, repeated 10 times) [ModuleOutput] [ModuleOutput] Summary of sample sizes: 1386, 1386, 1387, 1387, 1385, 1385, … [ModuleOutput] [ModuleOutput] Resampling results across tuning parameters: [ModuleOutput] [ModuleOutput] C RMSE Rsquared RMSE SD Rsquared SD [ModuleOutput] 0.25 276 0.946 30.6 0.00822
[ModuleOutput] 0.5 203 0.96 21.4 0.00754
[ModuleOutput] 1 180 0.965 17.7 0.00671
[ModuleOutput] 2 168 0.969 15.7 0.00614
[ModuleOutput] 4 158 0.972 14.9 0.0055
[ModuleOutput] 8 150 0.975 14.7 0.00506
[ModuleOutput] 16 146 0.976 14.7 0.00481
[ModuleOutput] 32 144 0.976 14.7 0.00477
[ModuleOutput] 64 143 0.977 14.6 0.00474
[ModuleOutput] 128 140 0.977 14.3 0.00469
[ModuleOutput] 256 139 0.978 15 0.00485
[ModuleOutput] 512 137 0.978 14.9 0.00484
[ModuleOutput] 1020 136 0.979 15.3 0.00494
[ModuleOutput] 2050 135 0.979 15.6 0.00513
[ModuleOutput] 4100 136 0.979 15.5 0.0051
[ModuleOutput] 8190 137 0.978 15.7 0.00518
[ModuleOutput] 16400 139 0.978 16.5 0.00551
[ModuleOutput] 32800 141 0.977 17.6 0.006
[ModuleOutput] 65500 145 0.976 19.4 0.00659
[ModuleOutput] 131000 151 0.974 20.8 0.00718
[ModuleOutput] 262000 161 0.97 27.2 0.0104
[ModuleOutput] 524000 478 0.8 325 0.172
[ModuleOutput] 1050000 1180 0.5 1010 0.204
[ModuleOutput] 2100000 3240 0.148 2260 0.117
[ModuleOutput] 4190000 6000 0.0604 6400 0.0527
[ModuleOutput] [ModuleOutput] Tuning parameter ‘sigma’ was held constant at a value of 0.0037653 [ModuleOutput] RMSE was used to select the optimal model using the smallest value. [ModuleOutput] The final values used for the model were sigma = 0.00377 and C = 2048.

run again with a tune grid

svmRadTuneGrid <- data.frame(.sigma = c(0.0037653,0.0037653,0.0037653,0.0037653,0.0037653),
                            .C = c(2048*.7,2048*.9,2048,2048*1.1,2048*1.3))

fitControl <- trainControl(
  method = "repeatedcv", #repeatedcv, 
  number = 10, #4
  repeats = 10, #10
  allowParallel = TRUE,
  summaryFunction = defaultSummary)

registerDoSEQ()
registerDoMC(cores = 6)

svmRadGridModel = train(Hunters ~ ., data = COElkHunters,
                      method = "svmRadial",
                      tuneGrid = svmRadTuneGrid,
                      trControl = fitControl)
svmRadGridModel
Support Vector Machines with Radial Basis Function Kernel 

1540 samples
   4 predictor

No pre-processing
Resampling: Cross-Validated (10 fold, repeated 10 times) 
Summary of sample sizes: 1385, 1385, 1385, 1386, 1386, 1386, ... 
Resampling results across tuning parameters:

  C       RMSE      Rsquared   MAE     
  1433.6  139.2026  0.9774819  94.04650
  1843.2  139.0134  0.9775285  93.81404
  2048.0  139.0110  0.9775067  93.69544
  2252.8  139.0443  0.9774694  93.63952
  2662.4  139.1025  0.9774175  93.69682

Tuning parameter 'sigma' was held constant at a value of 0.0037653
RMSE was used to select the optimal model using the smallest value.
The final values used for the model were sigma = 0.0037653 and C = 2048.

Best RMSE, not sure why caret is selecting parameters with higher RMSE, lets select manually

RSMEsvmRad <- filter(svmRadGridModel$results, RMSE == min(RMSE))

run again with a tune grid

svmRadTuneGrid2 <- data.frame(.sigma = c(RSMEsvmRad$sigma*.7,RSMEsvmRad$sigma*.9,RSMEsvmRad$sigma,RSMEsvmRad$sigma*1.1,RSMEsvmRad$sigma*1.3),
                             .C = c(RSMEsvmRad$C,RSMEsvmRad$C,RSMEsvmRad$C,RSMEsvmRad$C,RSMEsvmRad$C))

registerDoSEQ()
registerDoMC(cores = 6)

svmRadGridModel2 = train(Hunters ~ ., data = COElkHunters,
                        method = "svmRadial",
                        tuneGrid = svmRadTuneGrid2,
                        trControl = fitControl)
svmRadGridModel2
Support Vector Machines with Radial Basis Function Kernel 

1540 samples
   4 predictor

No pre-processing
Resampling: Cross-Validated (10 fold, repeated 10 times) 
Summary of sample sizes: 1385, 1386, 1386, 1387, 1385, 1386, ... 
Resampling results across tuning parameters:

  sigma       RMSE      Rsquared   MAE     
  0.00263571  137.7150  0.9779063  92.80793
  0.00338877  137.2732  0.9780733  92.75156
  0.00376530  137.2189  0.9780935  92.77121
  0.00414183  137.5238  0.9780036  93.07738
  0.00489489  138.4695  0.9777092  94.14245

Tuning parameter 'C' was held constant at a value of 2048
RMSE was used to select the optimal model using the smallest value.
The final values used for the model were sigma = 0.0037653 and C = 2048.
RSMEsvmRad <- filter(svmRadGridModel2$results, RMSE == min(RMSE))

Work thru some resampling methods with best kknn params

svmRadTuneGrid3 <- data.frame(.sigma = RSMEsvmRad$sigma,
                            .C = RSMEsvmRad$C)

trainmethods <- c("boot", "boot632", "optimism_boot", "cv", "repeatedcv", "LOOCV", "LGOCV", "none")
trainmethodperformance_all <- NULL
for (itrainmethod in trainmethods) {
  trainmethodperformance <- NULL
  fitControl <- trainControl(
    method = itrainmethod,
    number = 10, #4
    repeats = 10, #10
    allowParallel = TRUE,
    summaryFunction = defaultSummary)
  
  registerDoSEQ()
  registerDoMC(cores = 6)
  
  svmRadTrainModel = train(Hunters ~ ., data = COElkHunters,
                         method = "svmRadial",
                         tuneGrid = svmRadTuneGrid3,
                         trControl = fitControl)
  
  print(svmRadTrainModel)
  trainmethodperformance <- filter(svmRadTrainModel$results, RMSE == min(RMSE))
  trainmethodperformance$trainmethod <- itrainmethod
  trainmethodperformance_all <- rbind.fill(trainmethodperformance_all,trainmethodperformance)
}
trainmethodperformance_all
      sigma    C     RMSE  Rsquared       MAE    RMSESD  RsquaredSD      MAESD   trainmethod RMSEApparent
1 0.0037653 2048 360.4005 0.7726028 231.54142 282.30458 0.298028249 191.167393          boot           NA
2 0.0037653 2048 193.7360 0.9278034 123.00804 200.64763 0.209256870 132.918016       boot632      94.5999
3 0.0037653 2048 131.0365 0.9805517  83.53679  19.64063 0.008462524   7.577739 optimism_boot      94.5999
4 0.0037653 2048 135.4261 0.9789121  92.45378  11.02942 0.003095162   5.487161            cv           NA
5 0.0037653 2048 141.6488 0.9759367  94.08007  32.98579 0.017374430   9.333094    repeatedcv           NA
6 0.0037653 2048 133.2932 0.9794390  90.18903        NA          NA         NA         LOOCV           NA
7 0.0037653 2048 150.4885 0.9732360  99.97503  13.06248 0.004776229   6.047319         LGOCV           NA
  RsquaredApparent MAEApparent RMSEOptimism RsquaredOptimism MAEOptimism
1               NA          NA           NA               NA          NA
2        0.9898111    69.14552           NA               NA          NA
3        0.9898111    69.14552     36.43665     -0.009259344    14.39127
4               NA          NA           NA               NA          NA
5               NA          NA           NA               NA          NA
6               NA          NA           NA               NA          NA
7               NA          NA           NA               NA          NA
fitControl <- trainControl(
  method = "optimism_boot",
  number = 10, #4
  allowParallel = TRUE,
  summaryFunction = defaultSummary)

svmRadFinalTrainModel = train(Hunters ~ ., data = COElkHunters,
                            method = "svmRadial",
                            tuneGrid = svmRadTuneGrid3,
                            trControl = fitControl)

save off for future loading

save(svmRadFinalTrainModel, file = "~/_code/colorado-dow/datasets/svmRadFinalTrainModel.RData")

back to train vs test data for one more performance measure and chart… even though for future data we will use the final trained model

svmRadTrainModel = train(Hunters ~ ., data = traindata,
                       method = "svmRadial",
                       tuneGrid = svmRadTuneGrid3,
                       trControl = fitControl)

check performance

predictdata <- predict(svmRadTrainModel, testdata)
postResample(pred = predictdata, obs = testdata$Hunters)

Chart performance of predicted

chartperformance <- data.frame(predicted = predictdata, observed = testdata$Hunters)
ggplot(chartperformance, aes(predicted,observed)) +
  geom_point() +
  labs(title="Performance of Number of Hunters Prediction", caption="source: cpw.state.co.us")

kknn performed better than svmRadial RMSE=130 vs 154

FinalHuntersmodel <- kknnFinalTrainModel
# FinalHuntersmodel <- svmRadFinalTrainModel
save(FinalHuntersmodel, file = "~/_code/colorado-dow/datasets/FinalHuntersmodel.RData")

Use the 2018 Draw data to predict the number of hunters in 2018

COElkDraw2018 <- filter(COElkDraw, Year == 2018)
COElkHunters2018 <- COElkDraw2018[, colnames(COElkDraw2018) %in% c("Unit",FinalHuntersmodel$coefnames)]

COElkHunters2018 <- as.data.frame(unique(COElkHunters$Unit))
colnames(COElkHunters2018) <- "Unit"
COElkHunters2018$Year <- 2018
COElkHunters2018 <- left_join(COElkHunters2018,filter(COElkDraw,Year==2018))
# Replace the draw data that don't have entries with 0
COElkHunters2018$Drawn[is.na(COElkHunters2018$Drawn)] <- 0
COElkHunters2018$Quota[is.na(COElkHunters2018$Quota)] <- 0

COElkHunters2018 <- COElkHunters2018[, colnames(COElkHunters2018) %in% c("Unit",FinalHuntersmodel$coefnames)]

COElkHunters2018$Hunters <- round(predict(FinalHuntersmodel, COElkHunters2018))

COElkHunters2018$Hunters[COElkHunters2018$Hunters<0] <- 0

Save off so we don’t have to recreate the model everytime we want the results

save(COElkHunters2018,file="COElkHunters2018.RData")

Total Elk Harvest

Statewide

Group seasons

COElkHuntersStatewide <- summarise(group_by(COElkRifleAll,Year,Unit),
                                  Hunters = sum(c(Hunters.Antlered,Hunters.Antlerless,Hunters.Either),na.rm = T))
COElkHunters2018b <- COElkHunters2018
# COElkHunters2018b$Year <- as.character(COElkHunters2018b$Year)

# Join 2018 to historic data
COElkHuntersAll <- rbind.fill(COElkHuntersStatewide,COElkHunters2018b)

# Group Units
COElkHuntersStatewide <- summarise(group_by(COElkHuntersAll,Year),
                                   Hunters = sum(Hunters))
ggplot(COElkHuntersStatewide, aes(Year,Hunters)) +
  geom_bar(stat="identity",fill=ggthemes_data$hc$palettes$default[2]) +
  coord_cartesian(ylim = c(130000,155000)) +
  labs(title="Statewide Elk Hunters", caption="source: cpw.state.co.us")

TODO commentary


Hunters by Unit

I’d like to know where the hunters are distributed across the state.

Next year’s data

Year2018 <- filter(COElkHuntersAll, Year == "2018")
HunterstoPlot <- left_join(Unitboundaries2,Year2018, by=c("Unit"))
ggplot(HunterstoPlot, aes(long, lat, group = group)) + 
  geom_polygon(aes(fill = Hunters),colour = "grey50", size = .2) + #Unit boundaries
  geom_path(data = COroads,aes(x = long, y = lat, group = group), color="#3878C7",size=2) + #Roads
  geom_text(data=data_centroids,aes(x=longitude,y=latitude,label = Unit),size=3) + #Unit labels
  scale_fill_distiller(palette = "Oranges",direction = 1,na.value = 'light grey') +
  xlab("") + ylab("") +
  labs(title="Predicted 2018 Colorado Elk Hunters", caption="source: cpw.state.co.us")

TODO - commentary


Number of Hunters Rank of the Units

Would also be beneficial to rank each unit so I can reference later. In this case average the number of hunters of the last few years

HunterRank2018 <- filter(COElkHuntersAll, as.numeric(Year) == 2018)
HunterRank2018 <- summarise(group_by(HunterRank2018,Unit),
                             Hunters = mean(Hunters,na.rm = T))
HunterRank2018$HuntersRank = rank(-HunterRank2018$Hunters)

HunterRank2018 <- filter(HunterRank2018, HuntersRank <= 50) # top 50 units
# In order for the chart to retain the order of the rows, the X axis variable (i.e. the categories) has to be converted into a factor.
HunterRank2018 <- HunterRank2018[order(-HunterRank2018$Hunters), ]  # sort
HunterRank2018$Unit <- factor(HunterRank2018$Unit, levels = HunterRank2018$Unit)  # to retain the order in plot.

Lollipop Chart

ggplot(HunterRank2018, aes(x=Unit, y=Hunters)) + 
  geom_point(size=3) + 
  geom_segment(aes(x=Unit, 
                   xend=Unit, 
                   y=0, 
                   yend=Hunters)) + 
  labs(title="Predicted Elk Hunters 2018\nTop 50 Units", subtitle="Hunters by Unit", caption="source: cpw.state.co.us")

TODO - commentary


Conclusion

TODO

LS0tCnRpdGxlOiAiUHJlZGljdCBOdW1iZXIgb2YgRnV0dXJlIEVsayBIdW50ZXJzIgphdXRob3I6ICJQaWVycmUgU2Fybm93IgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IGZhbHNlCiAgICB0aGVtZTogeWV0aQogICAgaGlnaHRsaWdodDogZGVmYXVsdAotLS0KCgoqKioKIyMgRGVzY3JpcHRpb24KVXNlIGhpc3RvcmljYWwgZHJhdyByZXN1bHRzLCBhbmQgbnVtYmVyIG9mIGh1bnRlcnMgdG8gdHJhaW4gYSBtb2RlbCB3ZSBjYW4gdXNlIHRvIApwcmVkaWN0IHRoZSBudW1iZXIgb2YgaHVudGVycyBpbiBmdXR1cmUgeWVhcnMuCgpUT0RPIC0gSW5jbHVkZSBvdGhlciBwb3RlbnRpYWwgaW5wdXRzIHRoYXQgY291bGQgaW1wYWN0IGhvdyBtYW55IGh1bnRlcnMgZ2V0IGEgbGljZW5zZQphbmQgc2hvdyB1cC4gVGhvc2UgY291bGQgaW5jbHVkZSBlY29ub21pYyBpbmRpY2F0b3JzLCBhbmQgY29zdHMgYXNzb2NpYXRlZCB3aXRoIGh1bnRpbmcKKHRyYW5zcG9ydGF0aW9uLCBsb2RnaW5nKS4KCipfX05PVElDRV9fIHRoYXQgSSBhbSBvbmx5IGxvb2tpbmcgYXQgdGhlIGdlbmVyYWwgcmlmbGUgaHVudGluZyBzZWFzb25zIG9uIHB1YmxpYyBsYW5kLiBUaGVyZSBhcmUgYWxzbyAKaHVudGVycyBmb3IgQXJjaGVyeSwgTXV6emxlbG9hZGVyLCBQcml2YXRlIExhbmQsIFJhbmNoaW5nIGZvciBXaWxkbGlmZSwgZXRjLioKCioqKgojIyBTZXR1cApMb2FkIHJlcXVpcmVkIGxpYnJhcmllcyBmb3Igd3JhbmdsaW5nIGRhdGEsIGNoYXJ0aW5nLCBhbmQgbWFwcGluZwpgYGB7cn0KbGlicmFyeShwbHlyLHF1aWV0bHkgPSBUKSAjIGRhdGEgd3JhbmdsaW5nCmxpYnJhcnkoZHBseXIscXVpZXRseSA9IFQpICMgZGF0YSB3cmFuZ2xpbmcKbGlicmFyeShnZ3Bsb3QyLCBxdWlldGx5ID0gVCkgIyBjaGFydGluZwpsaWJyYXJ5KGdndGhlbWVzLHF1aWV0bHkgPSBUKSAjIHNvIEkgY2FuIGFkZCB0aGUgaGlnaGNoYXJ0cyB0aGVtZSBhbmQgcGFsZXR0ZQpsaWJyYXJ5KHNjYWxlcyxxdWlldGx5ID0gVCkgIyB0byBsb2FkIHRoZSBwZXJjZW50IGZ1bmN0aW9uIHdoZW4gbGFiZWxpbmcgcGxvdHMKbGlicmFyeShjYXJldCxxdWlldGx5ID0gVCkgIyBjbGFzc2lmaWNhdGlvbiBhbmQgcmVncmVzc2lvbiB0cmFpbmluZwpsaWJyYXJ5KGZvcmVhY2gscXVpZXRseSA9IFQpICMgcGFyYWxsZWwgcHJvY2Vzc2luZyB0byBzcGVlZCB1cCB0aGUgbW9kZWwgdHJhaW5pbmcKbGlicmFyeShkb01DLHF1aWV0bHkgPSBUKSAjIHBhcmFsbGVsIHByb2Nlc3NpbmcgdG8gc3BlZWQgdXAgdGhlIG1vZGVsIHRyYWluaW5nCmxpYnJhcnkobHVicmlkYXRlLHF1aWV0bHkgPSBUKSAjIGZvciB0aW1pbmcgbW9kZWxzCmBgYAoKU2V0IG91ciBwcmVmZXJyZWQgY2hhcnRpbmcgdGhlbWUKYGBge3J9CnRoZW1lX3NldCh0aGVtZV9taW5pbWFsKCkrdGhlbWVfaGMoKSt0aGVtZShsZWdlbmQua2V5LndpZHRoID0gdW5pdCgxLjUsICJjbSIpKSkKYGBgCgpSdW4gc2NyaXB0IHRvIGdldCBodW50ZXIgZGF0YQpgYGB7cn0Kc291cmNlKCd+L19jb2RlL2NvbG9yYWRvLWRvdy9kYXRhc2V0cy9Db2xvcmFkbyBFbGsgSGFydmVzdCBEYXRhLlInLCBlY2hvPUYpCmBgYAoKVGFibGUgb2YgdGhlIGhhcnZlc3QgZGF0YQpDT0Vsa1JpZmxlQWxsCgpSdW4gc2NyaXB0IHRvIGdldCBkcmF3IGRhdGEKYGBge3J9CnNvdXJjZSgnfi9fY29kZS9jb2xvcmFkby1kb3cvZGF0YXNldHMvRWxrIERyYXdpbmcgU3VtbWFyaWVzLlInLCBlY2hvPUYpCmBgYAoKVGFibGUgb2YgdGhlIGRhdGEKYGBge3J9CmhlYWQoQ09FbGtEcmF3QWxsKQpgYGAKCnNvdXJjZSBnZW9kYXRhCmBgYHtyfQpzb3VyY2UoJ34vX2NvZGUvY29sb3JhZG8tZG93L2RhdGFzZXRzL0NvbG9yYWRvIEdNVW5pdCBhbmQgUm9hZCBkYXRhLlInLCBlY2hvPUYpCmBgYAoKVGFrZSBhIHBlYWsgYXQgdGhlIGJvdW5kYXJ5IGRhdGEKYGBge3J9CmhlYWQoVW5pdGJvdW5kYXJpZXMyKQpgYGAKClNldCB0byBwcmVkaWN0aXZlIGFuYWx5dGljcyBkaXJlY3RvcnkKYGBge3J9CnNldHdkKCJ+L19jb2RlL2NvbG9yYWRvLWRvdy9waGFzZSBJSUkgLSBwcmVkaWN0aXZlIGFuYWx5dGljcyIpCmBgYAoKKioqCiMjIE9yZ2FuaXplIGRhdGEKV2lsbCBzdGFydCBieSBncm91cGluZyBhbGwgb2YgdGhlIHNlYXNvbnMgdG9nZXRoZXIsIGFuZCBtb2RlbGluZyB0aGUgbnVtYmVyIG9mIGh1bnRlcnMgcGVyClllYXIgYW5kIFVuaXQKR3JvdXAgRHJhdyByZXN1bHRzIGRhdGEgYnkgWWVhciBhbmQgVW5pdApgYGB7cn0KQ09FbGtEcmF3IDwtIHN1bW1hcmlzZShncm91cF9ieShDT0Vsa0RyYXdBbGwsWWVhcixVbml0KSwKICAgICAgICAgICAgICAgICAgICAgICBRdW90YSA9IHN1bShPcmlnX1F1b3RhLG5hLnJtID0gVCksCiAgICAgICAgICAgICAgICAgICAgICAgRHJhd24gPSBzdW0oQ2hjc19EcmF3bixuYS5ybSA9IFQpKQpgYGAKCkFwcHJvcHJpYXRlIGZpZWxkIGNsYXNzZXMgZm9yIG1vZGVsIHRyYWluaW5nCmBgYHtyfQpDT0Vsa0RyYXckWWVhciA8LSBhcy5udW1lcmljKENPRWxrRHJhdyRZZWFyKQpgYGAKCkdyb3VwIEh1bnRlciBkYXRhIGJ5IFllYXIgYW5kIFVuaXQKYGBge3J9CkNPRWxrSHVudGVycyA8LSBzdW1tYXJpc2UoZ3JvdXBfYnkoQ09FbGtSaWZsZUFsbCxZZWFyLFVuaXQpLAogICAgICAgICAgICAgICAgICAgICAgICAgIEh1bnRlcnMgPSBzdW0oYyhIdW50ZXJzLkFudGxlcmVkLEh1bnRlcnMuQW50bGVybGVzcyxIdW50ZXJzLkVpdGhlciksbmEucm0gPSBUKSkKCkNPRWxrSHVudGVycyRZZWFyIDwtIGFzLm51bWVyaWMoQ09FbGtIdW50ZXJzJFllYXIpCmBgYAoKSm9pbiBpbiBIdW50ZXIgYW5kIERyYXcgZGF0YSB0b2dldGhlcgpgYGB7cn0KQ09FbGtIdW50ZXJzIDwtIGxlZnRfam9pbihDT0Vsa0h1bnRlcnMsIENPRWxrRHJhdywgYnkgPSBjKCJZZWFyIiwiVW5pdCIpKQpgYGAKClJlcGxhY2UgdGhlIGRyYXcgZGF0YSB0aGF0IGRvbid0IGhhdmUgZW50cmllcyB3aXRoIDAKYGBge3J9CkNPRWxrSHVudGVycyREcmF3bltpcy5uYShDT0Vsa0h1bnRlcnMkRHJhd24pXSA8LSAwCkNPRWxrSHVudGVycyRRdW90YVtpcy5uYShDT0Vsa0h1bnRlcnMkUXVvdGEpXSA8LSAwCmBgYAoKU3BsaXQgaW50byB0cmFpbiBhbmQgdGVzdCBzZXRzLiBXaWxsIHVzZSA3NSUgb2YgdGhlIGRhdGEgdG8gdHJhaW4gb24uIEJlIHN1cmUgdG8gaW5jbHVkZQplYWNoIHVuaXQgaW4gdGhlIHNwbGl0LiAuLi4gc28gZG8gdGhlIHNwbGl0IGZvciBlYWNoIHVuaXQsIGZpcnN0IG1ha2Ugc3VyZSBlYWNoIFVuaXQgaGFzCmF0IGxlYXN0IHRocmVlIGVudHJpZXMKCmBgYHtyfQpDT0Vsa0h1bnRlcnMgPC0gbXV0YXRlKGdyb3VwX2J5KENPRWxrSHVudGVycyxVbml0KSwKICAgICAgICAgICAgICAgICAgICAgICBudW1lbnRyaWVzID0gbigpKQpDT0Vsa0h1bnRlcnMgPC0gZmlsdGVyKENPRWxrSHVudGVycywgbnVtZW50cmllcyA+PSAzKQpDT0Vsa0h1bnRlcnMkVW5pdFllYXIgPC0gcGFzdGUoQ09FbGtIdW50ZXJzJFVuaXQsIENPRWxrSHVudGVycyRZZWFyKQoKdHJhaW5kYXRhIDwtIENPRWxrSHVudGVycyAlPiUgZ3JvdXBfYnkoVW5pdCkgJT4lIHNhbXBsZV9mcmFjKHNpemUgPSAuNzUsIHJlcGxhY2UgPSBGKQp0ZXN0ZGF0YSA8LSBDT0Vsa0h1bnRlcnNbIUNPRWxrSHVudGVycyRVbml0WWVhciAlaW4lIHRyYWluZGF0YSRVbml0WWVhcixdCgpDT0Vsa0h1bnRlcnMgPC0gc2VsZWN0KENPRWxrSHVudGVycywgLVVuaXRZZWFyLCAtbnVtZW50cmllcykKCnRyYWluZGF0YSA8LSBzZWxlY3QodHJhaW5kYXRhLCAtVW5pdFllYXIsIC1udW1lbnRyaWVzKQp0ZXN0ZGF0YSA8LSBzZWxlY3QodGVzdGRhdGEsIC1Vbml0WWVhciwgLW51bWVudHJpZXMpCmBgYAoKU2F2ZSBvZmYgZm9yIGltcG9ydGluZyBpbnRvIEF6dXJlTUwKYGBge3J9CndyaXRlLmNzdihDT0Vsa0h1bnRlcnMsZmlsZSA9ICJ+L19jb2RlL2NvbG9yYWRvLWRvdy9kYXRhc2V0cy9DT0Vsa0h1bnRlcnMuY3N2Iixyb3cubmFtZXMgPSBGKQpgYGAKCm5vdGljZSB0aGF0IHRoZSBudW1iZXIgb2YgaHVudGVycyBkYXRhIGlzIHNrZXdlZC4KYGBge3IgZmlnLndpZHRoPTEwfQpnZ3Bsb3QoQ09FbGtIdW50ZXJzLCBhZXMoSHVudGVycykpICsgCiAgZ2VvbV9kZW5zaXR5KCkgKwogIHhsYWIoIkh1bnRlcnMgaW4gVW5pdCIpICsKICB5bGFiKCJOdW1iZXIgb2YgVW5pdHMiKSArCiAgdGhlbWUoYXhpcy50ZXh0LnkgPSBlbGVtZW50X2JsYW5rKCkpICsKICBsYWJzKHRpdGxlPSJEaXN0cmlidXRpb24gb2YgSHVudGVycyBpbiBlYWNoIFVuaXQiLCBzdWJ0aXRsZT0iMjAwNi0yMDE3IiwgY2FwdGlvbj0ic291cmNlOiBjcHcuc3RhdGUuY28udXMiKQoKYGBgCgoKQSBnZW5lcmFsIHJ1bGUgb2YgdGh1bWIgdG8gY29uc2lkZXIgaXMgdGhhdCBza2V3ZWQgZGF0YSB3aG9zZSByYXRpbyBvZiB0aGUgaGlnaGVzdCB2YWx1ZSB0byB0aGUgCmxvd2VzdCB2YWx1ZSBpcyBncmVhdGVyIHRoYW4gMjAgaGF2ZSBzaWduaWZpY2FudCBza2V3bmVzcy4gQWxzbywgdGhlIHNrZXduZXNzIHN0YXRpc3RpYyBjYW4gYmUgCnVzZWQgYXMgYSBkaWFnbm9zdGljLiBJZiB0aGUgcHJlZGljdG9yIGRpc3RyaWJ1dGlvbiBpcyByb3VnaGx5IHN5bW1ldHJpYywgdGhlIHNrZXduZXNzIHZhbHVlcyAKd2lsbCBiZSBjbG9zZSB0byB6ZXJvLiBBcyB0aGUgZGlzdHJpYnV0aW9uIGJlY29tZXMgbW9yZSByaWdodCBza2V3ZWQsIHRoZSBza2V3bmVzcyBzdGF0aXN0aWMgCmJlY29tZXMgbGFyZ2VyLiBTaW1pbGFybHksIGFzIHRoZSBkaXN0cmlidXRpb24gYmVjb21lcyBtb3JlIGxlZnQgc2tld2VkLCB0aGUgdmFsdWUgYmVjb21lcyBuZWdhdGl2ZS4KUmVwbGFjaW5nIHRoZSBkYXRhIHdpdGggdGhlIGxvZywgc3F1YXJlIHJvb3QsIG9yIGludmVyc2UgbWF5IGhlbHAgdG8gcmVtb3ZlIHRoZSBza2V3LgoKRXhhbXBsZSBvZiBob3cgQm94Q294IGNhbiByZWRpc3RyaWJ1dGUgdGhlIGRhdGEKYGBge3J9CnByZVByb2NWYWx1ZXMyIDwtIHByZVByb2Nlc3MoYXMuZGF0YS5mcmFtZSh0cmFpbmRhdGEpLCBtZXRob2QgPSAiQm94Q294IikKdHJhaW5CQyA8LSBwcmVkaWN0KHByZVByb2NWYWx1ZXMyLCBhcy5kYXRhLmZyYW1lKHRyYWluZGF0YSkpCmBgYAoKYGBge3IgZmlnLndpZHRoPTEwfQpnZ3Bsb3QodHJhaW5CQywgYWVzKEh1bnRlcnMpKSArIAogIGdlb21fZGVuc2l0eSgpICsKICB4bGFiKCJCb3hDb3ggSHVudGVycyBpbiBVbml0IikgKwogIHlsYWIoIk51bWJlciBvZiBVbml0cyIpICsKICB0aGVtZShheGlzLnRleHQueSA9IGVsZW1lbnRfYmxhbmsoKSkgKwogIGxhYnModGl0bGU9IkJveENveCBEaXN0cmlidXRpb24gb2YgSHVudGVycyBpbiBlYWNoIFVuaXQiLCBzdWJ0aXRsZT0iMjAwNi0yMDE3IiwgY2FwdGlvbj0ic291cmNlOiBjcHcuc3RhdGUuY28udXMiKQpgYGAKY2FyZXQgaGFzIGEgcHJlcHJvY2Nlc3MgZnVuY3Rpb24gZm9yIGNvcnJlY3RpbmcgZm9yIHNrZXduZXNzICdCb3hDb3gnLCB3ZSB3aWxsIG5lZWQgdG8gYmUgc3VyZSB0bwpsb29rIGF0IHVzaW5nIHRoaXMgZnVuY3Rpb24gaW4gdGhlIHRyYWluaW5nIG1vZGVscy4KCiMjIE1vZGVsIEJ1aWxkaW5nClRoaXMgaXMgcXVpdGUgYW4gaXRlcmF0aXZlIHByb2Nlc3MuIEl0IGlzIGltcG9ydGFudCB0byBkb2N1bWVudCBhbmQgc2F2ZSBvZmYgZGF0YS4KUnVuIHRocnUgZGlmZmVyaW5nICdxdWljayB0byB0cmFpbicgbWV0aG9kcwpXaGljaCBvbmUgcGVyZm9ybWVkIHRoZSBiZXN0PwpEZXRlcm1pbmUgb3RoZXIgc2ltaWxhciBtZXRob2RzIGFuZCBydW4gdGhlbS4KV2hpY2ggb25lIHBlcmZvcm1lZCB0aGUgYmVzdD8KRGV0ZXJtaW5lIGRpc2ltbWlsYXIgbWV0aG9kcy4KV2hpY2ggb25lIHBlcmZvcm1lZCB0aGUgYmVzdD8KCk5vdyBsZXRzIHdvcmsgb24gc29tZSByZWZpbmVkIHR1bmluZy4KQXNzZXNzIHByZXByb2Nlc3NpbmcgZnVuY3Rpb25zLiBjZW50ZXIsIHNjYWxlLCBwY2EsIGJveGNveCwgbnp2LCBldGMKc29tZSB1bml0cyBoYXZlIHZlcnkgbGl0dGxlIGRhdGEsIHNob3VsZCB3ZSByZW1vdmUgdGhlbT8gJ3plcm8gdmFyaWFuY2VzCjEyMywgNzkxLCA4NywgOTQsIDg4ClRPRE8sIGZyZXF1ZW5jeSBwbG90IG9mIFVuaXQKClJ1biB0aGUgbGlzdCBvZiBvdGhlciBmYXZvcmFibGUgbWV0aG9kcyB3aXRoIHByZXByb2Nlc3NpbmcgaW4gcGxhY2UKRnVydGhlciB0dW5lIG1ldGhvZCdzIHBhcmFtZXRlcnMKCklzIHRoZXJlIGEgbWV0aG9kIHRoYXQgaXMgYmVzdD8gIEFyZSBzb21lIG1ldGhvZHMgYmV0dGVyIGF0IHBhcnQgb2YgdGhlIGRhdGEKdGhhbiBvdGhlcnM/IG1heWJlIHdlIGNvbWJpbmU/CgpJbnNlcnQgaW50byBBenVyZU1MIHRvIGZ1cnRoZXIgaW5zcGVjdGlvbnMgYW5kIGNyZWF0ZSBpbnRvIGEgd2Vic2VydmljZQpJZiB0aGUgcGFja2FnZXMgb3IgbWV0aG9kcyBhcmUgbm90IHlldCBzdXBwb3J0ZWQgaW4gQXp1cmUsIHdlIHdpbGwgbmVlZCB0byBjcmVhdGUgYW4gUiBNb2RlbAppbnN0ZWFkIG9mIGp1c3QgcnVubmluZyBhbiByc2NyaXB0LgoKQWRkaXRvbmFsbHksIEF6dXJlTUwgaGFzIHNvbWUgYWRkaXRpb25hbCBtZXRob2RzIHRvIGNvbnNpZGVyLCBlbnN1cmUgd2UgYXR0ZW1wdCB0byB1c2UgdGhvc2UgYXMgd2VsbC4KCgpTdGVwIDEgLSBMb29wIHRocm91Z2ggcG9zc2libGUgbWV0aG9kcywgdXRpbGl6aW5nIHRoZSBxdWlja2VyICdhZGFwdGl2ZV9jdicgcGFyYW1ldGVyIHNlYXJjaCBmcm9tIGNhcmV0LgojIENvbnNpZGVyIHNjcmlwdGluZyB0aGlzIGludG8gQXp1cmVNTCB0byBtYWtlIGl0IHJ1biBtdWNoIGZhc3RlciwgdGhvdWdoIHRoZXJlIGlzIG1vcmUgc2V0dXAgYW5kIGVycm9ycyB0byAKY29udHJvbCBmb3IKCmBgYHtyfQpxdWlja21ldGhvZHMgPC0gYygibG0iLCdzdm1MaW5lYXInLCJzdm1SYWRpYWwiLCJrbm4iLCJjdWJpc3QiLCJra25uIiwiZ2xtLm5iIikKCnN0ZXAxX2FsbCA8LSBOVUxMCmZvciAoaW1ldGhvZCBpbiBxdWlja21ldGhvZHMpIHsKICBzdGVwMSA8LSBOVUxMCiAgc3RhcnQgPC0gbm93KCkKICAKICBpZiAoaW1ldGhvZCA9PSAibG0iKSB7CiAgICBjb250cm9sbWV0aG9kIDwtICJyZXBlYXRlZGN2IgogIH0gZWxzZSB7Y29udHJvbG1ldGhvZCA8LSAiYWRhcHRpdmVfY3YifQogIAogIGZpdENvbnRyb2wgPC0gdHJhaW5Db250cm9sKAogICAgbWV0aG9kID0gY29udHJvbG1ldGhvZCwKICAgICMgc2VhcmNoID0gJ3JhbmRvbScsCiAgICBudW1iZXIgPSA0LAogICAgcmVwZWF0cyA9IDQsCiAgICBhbGxvd1BhcmFsbGVsID0gVFJVRSwKICAgIHN1bW1hcnlGdW5jdGlvbiA9IGRlZmF1bHRTdW1tYXJ5KQogIAogIHJlZ2lzdGVyRG9TRVEoKQogIHJlZ2lzdGVyRG9NQyhjb3JlcyA9IDYpCiAgCiAgSHVudGVyc01vZGVsXzEgPSB0cmFpbihIdW50ZXJzIH4gLiwgZGF0YSA9IHRyYWluZGF0YSwKICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9IGltZXRob2QsCiAgICAgICAgICAgICAgICAgICAgICAgICBwcmVQcm9jID0gYygiY2VudGVyIiwic2NhbGUiKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICB0dW5lTGVuZ3RoID0gMTUsCiAgICAgICAgICAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSBmaXRDb250cm9sKQogIAogIEh1bnRlcnNNb2RlbF8xCiAgCiAgIyBtZWFzdXJlIHBlcmZvcm1hbmNlCiAgcHJlZGljdGRhdGEgPC0gcHJlZGljdChIdW50ZXJzTW9kZWxfMSwgdGVzdGRhdGEpCiAgCiAgc3RlcDEkbWV0aG9kIDwtIGltZXRob2QKICBzdGVwMSRSTVNFIDwtIHBvc3RSZXNhbXBsZShwcmVkID0gcHJlZGljdGRhdGEsIG9icyA9IHRlc3RkYXRhJEh1bnRlcnMpWzFdCiAgc3RlcDEkZHVyYXRpb24gPC0gbm93KCkgLSBzdGFydAogIHN0ZXAxIDwtIGFzLmRhdGEuZnJhbWUoc3RlcDEpCiAgc3RlcDFfYWxsIDwtIHJiaW5kKHN0ZXAxX2FsbCxzdGVwMSkKfQpgYGAKVmlldyBSZXN1bHRzCmBgYHtyfQpzdGVwMV9hbGwKYGBgCmBgYHtyfQp0b3BfdHdvX21vZGVscyA8LSB0b3BfbihzdGVwMV9hbGwsMiwtUk1TRSkkbWV0aG9kCmBgYAoKVGFrZSB0aGUgdG9wIHR3byBhbmQgZGV0ZXJtaW5lIHNvbWUgYWRkaXRvbmFsIG1ldGhvZHMgdG8gdHJ5IGJ5IG1heGltaXppbmcgdGhlIEphY2NhcmQKZGlzc2ltaWxhcml0eSBiZXR3ZWVuIHNldHMgb2YgbW9kZWxzCmBgYHtyfQp0YWcgPC0gcmVhZC5jc3YoInRhZ19kYXRhLmNzdiIsIHJvdy5uYW1lcyA9IDEpCnRhZyA8LSBhcy5tYXRyaXgodGFnKQpgYGAKCiMjIFNlbGVjdCBvbmx5IG1vZGVscyBmb3IgcmVncmVzc2lvbgpgYGB7cn0KcmVnTW9kZWxzIDwtIHRhZ1t0YWdbLCJSZWdyZXNzaW9uIl0gPT0gMSxdCgphbGwgPC0gMTpucm93KHJlZ01vZGVscykKZGlzc2ltaWxhcm1ldGhvZHNfYWxsIDwtIE5VTEwKZm9yIChpdG9wdHdvIGluIDE6MikgewogICMjIFNlZWQgdGhlIGFuYWx5c2lzIHdpdGggdGhlIG1vZGVsIG9mIGludGVyZXN0CiAgc3RhcnQgPC0gZ3JlcCh0b3BfdHdvX21vZGVsc1tpdG9wdHdvXSwgcm93bmFtZXMocmVnTW9kZWxzKSwgZml4ZWQgPSBUUlVFKQogIHBvb2wgPC0gYWxsW2FsbCAhPSBzdGFydF0KICAKICAjIyBTZWxlY3QgNCBtb2RlbCBtb2RlbHMgYnkgbWF4aW1pemluZyB0aGUgSmFjY2FyZAogICMjIGRpc3NpbWlsYXJpdHkgYmV0d2VlbiBzZXRzIG9mIG1vZGVscwogIG5leHRNb2RzIDwtIG1heERpc3NpbShyZWdNb2RlbHNbc3RhcnQsLGRyb3AgPSBGQUxTRV0sIAogICAgICAgICAgICAgICAgICAgICAgICByZWdNb2RlbHNbcG9vbCwgXSwgCiAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJKYWNjYXJkIiwKICAgICAgICAgICAgICAgICAgICAgICAgbiA9IDQpCiAgCiAgcm93bmFtZXMocmVnTW9kZWxzKVtjKG5leHRNb2RzKV0KICAKICBkaXNzaW1pbGFybWV0aG9kcyA8LSByb3duYW1lcyhyZWdNb2RlbHMpW25leHRNb2RzXQogIGRpc3NpbWlsYXJtZXRob2RzIDwtIHN0cl9leHRyYWN0KHN0cmluZyA9IGRpc3NpbWlsYXJtZXRob2RzLHBhdHRlcm4gPSAiWzphbG51bTpdKyg/PVxcKSkiKQogIGRpc3NpbWlsYXJtZXRob2RzX2FsbCA8LSBjKGRpc3NpbWlsYXJtZXRob2RzX2FsbCxkaXNzaW1pbGFybWV0aG9kcykKfQpgYGAKCk5vdyB3ZSBoYXZlIDggbW9yZSBtZXRob2RzIHRvIHRyeSBpbiB0aGUgc2FtZSBtYW5uZXIKYGBge3J9CmRpc3NpbWlsYXJtZXRob2RzX2FsbCA8LSB1bmlxdWUoZGlzc2ltaWxhcm1ldGhvZHNfYWxsKQpgYGAKYGBge3J9CmZvciAoaW1ldGhvZCBpbiBkaXNzaW1pbGFybWV0aG9kc19hbGwpIHsKICBzdGVwMSA8LSBOVUxMCiAgc3RhcnRfdGltZXIgPC0gbm93KClbMV0KICAKICBpZiAoaW1ldGhvZCA9PSAibG0iKSB7CiAgICBjb250cm9sbWV0aG9kIDwtICJyZXBlYXRlZGN2IgogIH0gZWxzZSB7Y29udHJvbG1ldGhvZCA8LSAiYWRhcHRpdmVfY3YifQogIAogIGZpdENvbnRyb2wgPC0gdHJhaW5Db250cm9sKAogICAgbWV0aG9kID0gY29udHJvbG1ldGhvZCwKICAgICMgc2VhcmNoID0gJ3JhbmRvbScsCiAgICBudW1iZXIgPSA0LAogICAgcmVwZWF0cyA9IDQsCiAgICBhbGxvd1BhcmFsbGVsID0gVFJVRSwKICAgIHN1bW1hcnlGdW5jdGlvbiA9IGRlZmF1bHRTdW1tYXJ5KQogIAogIHJlZ2lzdGVyRG9TRVEoKQogIHJlZ2lzdGVyRG9NQyhjb3JlcyA9IDYpCiAgCiAgSHVudGVyc01vZGVsXzEgPSB0cmFpbihIdW50ZXJzIH4gLiwgZGF0YSA9IHRyYWluZGF0YSwKICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9IGltZXRob2QsCiAgICAgICAgICAgICAgICAgICAgICAgICBwcmVQcm9jID0gYygiY2VudGVyIiwic2NhbGUiKSwgCiAgICAgICAgICAgICAgICAgICAgICAgICB0dW5lTGVuZ3RoID0gMTUsCiAgICAgICAgICAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSBmaXRDb250cm9sKQogIAogIEh1bnRlcnNNb2RlbF8xCiAgCiAgIyBtZWFzdXJlIHBlcmZvcm1hbmNlCiAgcHJlZGljdGRhdGEgPC0gcHJlZGljdChIdW50ZXJzTW9kZWxfMSwgdGVzdGRhdGEpCiAgCiAgc3RlcDEkbWV0aG9kIDwtIGltZXRob2QKICBzdGVwMSRSTVNFIDwtIHBvc3RSZXNhbXBsZShwcmVkID0gcHJlZGljdGRhdGEsIG9icyA9IHRlc3RkYXRhJEh1bnRlcnMpWzFdCiAgc3RlcDEkZHVyYXRpb24gPC0gbm93KClbMV0gLSBzdGFydF90aW1lclsxXQogIHN0ZXAxIDwtIGFzLmRhdGEuZnJhbWUoc3RlcDEpCiAgc3RlcDFfYWxsIDwtIHJiaW5kKHN0ZXAxX2FsbCxzdGVwMSkKfQpgYGAKCmBgYHtyfQpzdGVwMV9hbGwKYGBgCgoKTm93IGxldHMgd29yayBvbiBzb21lIHJlZmluZWQgdHVuaW5nIG9uIHRoZSB0b3AgbWV0aG9kcwpBbnkgdmFsdWFibGUgcHJlcHJvY2Vzc2luZyBzdGVwcz8KYGBge3J9CnByZXByb2Nlc3NmdW5jdGlvbnMgPC0gYygiQm94Q294IiwgIlllb0pvaG5zb24iLCAiZXhwb1RyYW5zIiwgImNlbnRlciIsICJzY2FsZSIsICJyYW5nZSIsICJrbm5JbXB1dGUiLCAiYmFnSW1wdXRlIiwgIm1lZGlhbkltcHV0ZSIsICJwY2EiLCAiaWNhIiwgInNwYXRpYWxTaWduIiwgImNvcnIiLCAienYiLCAibnp2IikKdG9wbWV0aG9kcyA8LSB0b3BfbihzdGVwMV9hbGwsMiwtUk1TRSkkbWV0aG9kCgpmaXRDb250cm9sIDwtIHRyYWluQ29udHJvbCgKICBtZXRob2QgPSAiYWRhcHRpdmVfY3YiLCAjcmVwZWF0ZWRjdiwgCiAgc2VhcmNoID0gJ3JhbmRvbScsCiAgbnVtYmVyID0gMTAsICM0CiAgcmVwZWF0cyA9IDEwLCAjMTAKICAjIGNsYXNzUHJvYnMgPSBUUlVFLAogICMgc2F2ZVByZWQgPSBUUlVFLAogIGFsbG93UGFyYWxsZWwgPSBUUlVFLAogIHN1bW1hcnlGdW5jdGlvbiA9IGRlZmF1bHRTdW1tYXJ5KQoKUFBwZXJmb3JtYW5jZV9hbGwgPC0gTlVMTApQUHBlcmZvcm1hbmNlIDwtIE5VTEwKZm9yIChpbWV0aG9kIGluIHRvcG1ldGhvZHMpIHsKICBmb3IgKGlwcmVwcm9jZXNzIGluIHByZXByb2Nlc3NmdW5jdGlvbnMpIHsKICAgIHJlZ2lzdGVyRG9TRVEoKQogICAgcmVnaXN0ZXJEb01DKGNvcmVzID0gNikKICAgIAogICAgUHJlUHJvY2Vzc01vZGVsID0gdHJhaW4oSHVudGVycyB+IC4sIGRhdGEgPSB0cmFpbmRhdGEsCiAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSBpbWV0aG9kLAogICAgICAgICAgICAgICAgICAgICAgICAgcHJlUHJvYyA9IGlwcmVwcm9jZXNzLCAKICAgICAgICAgICAgICAgICAgICAgICAgICN0dW5lTGVuZ3RoID0gMTAsCiAgICAgICAgICAgICAgICAgICAgICAgICAjdHVuZUdyaWQgPSBra25uVHVuZUdyaWQsCiAgICAgICAgICAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSBmaXRDb250cm9sKQogICAgCiAgICBwcmludChQcmVQcm9jZXNzTW9kZWwpCiAgICAKICAgICMgY2hlY2sgcGVyZm9ybWFuY2UKICAgIHByZWRpY3RkYXRhIDwtIHByZWRpY3QoUHJlUHJvY2Vzc01vZGVsLCB0ZXN0ZGF0YSkKICAgIAogICAgUFBwZXJmb3JtYW5jZSRtZXRob2QgPC0gaW1ldGhvZAogICAgUFBwZXJmb3JtYW5jZSRwcmVwcm9jZXNzIDwtIGlwcmVwcm9jZXNzCiAgICBQUHBlcmZvcm1hbmNlJFJNU0UgPC0gcG9zdFJlc2FtcGxlKHByZWQgPSBwcmVkaWN0ZGF0YSwgb2JzID0gdGVzdGRhdGEkSHVudGVycylbMV0KICAgIFBQcGVyZm9ybWFuY2UgPC0gYXMuZGF0YS5mcmFtZShQUHBlcmZvcm1hbmNlKQogICAgUFBwZXJmb3JtYW5jZV9hbGwgPC0gcmJpbmQoUFBwZXJmb3JtYW5jZV9hbGwsUFBwZXJmb3JtYW5jZSkKICB9Cn0KYGBgCmBgYHtyfQpQUHBlcmZvcm1hbmNlX2FsbApgYGAKCk91dHB1dCBmcm9tIEF6dXJlTUwKW01vZHVsZU91dHB1dF0gICAgICAgICAgbWV0aG9kIHByZXByb2Nlc3MgICAgIFJNU0UKW01vZHVsZU91dHB1dF0gUk1TRSAgICAgICBra25uICAgICBCb3hDb3ggMTMwLjc5MzkKW01vZHVsZU91dHB1dF0gUk1TRTEgICAgICBra25uIFllb0pvaG5zb24gMTMwLjk2MDAKW01vZHVsZU91dHB1dF0gUk1TRTIgICAgICBra25uICAgICBjZW50ZXIgMTMwLjczMzEKW01vZHVsZU91dHB1dF0gUk1TRTMgICAgICBra25uICAgICAgc2NhbGUgMTMwLjE4MTgKW01vZHVsZU91dHB1dF0gUk1TRTQgICAgICBra25uICAgICAgICBwY2EgMTMwLjIwNzEKW01vZHVsZU91dHB1dF0gUk1TRTUgc3ZtUmFkaWFsICAgICBCb3hDb3ggMTU0LjA4OTgKW01vZHVsZU91dHB1dF0gUk1TRTYgc3ZtUmFkaWFsIFllb0pvaG5zb24gMTY5Ljk4MTYKW01vZHVsZU91dHB1dF0gUk1TRTcgc3ZtUmFkaWFsICAgICBjZW50ZXIgMTU0LjE4OTEKW01vZHVsZU91dHB1dF0gUk1TRTggc3ZtUmFkaWFsICAgICAgc2NhbGUgMTU0LjEwMDAKW01vZHVsZU91dHB1dF0gUk1TRTkgc3ZtUmFkaWFsICAgICAgICBwY2EgMTY0LjA4ODEKc3ZtUmFkaWFsIGFuZCBra25uIGRvbid0IHBlcmZvcm0gYmV0dGVyIHdpdGggYW55IG9mIHRoZSBwcmVwcm9jZXNzaW5nIGZ1bmN0aW9ucyBpbiBwbGFjZQogIApOb3cgd2UgY2FuIHJldmlldyB0aGUgcHJlZGljdG9ycywgdGhlcmUgYXJlIG9ubHkgYSBmZXcgZmllbGRzIHNvIEkgd2lsbCBtYW51YWxseSB0ZXN0IHBlcmZvcm1hbmNlCndoaWxlIGV4Y2x1ZGluZyBlYWNoIG9mIHRoZW0gdG8gbW9uaXRvciB0aGVpciBpbXBvcnRhbmNlLgpTb21lIG9mIG91ciBmaWVsZHMgYXJlIGluc3RpbmN0aXZlbHkgcmVxdWlyZWQgKFllYXIsIFVuaXQpCgpgYGB7cn0KUHJlZGljdG9ycyA8LSBjKCJRdW90YSIsIkRyYXduIikKUHJlZGljdG9ycGVyZm9ybWFuY2VfYWxsIDwtIE5VTEwKUHJlZGljdG9ycGVyZm9ybWFuY2UgPC0gTlVMTApmb3IgKGltZXRob2QgaW4gdG9wbWV0aG9kcykgewogIGZvciAoaXByZWRpY3RvciBpbiBQcmVkaWN0b3JzKSB7CiAgICByZWdpc3RlckRvU0VRKCkKICAgIHJlZ2lzdGVyRG9NQyhjb3JlcyA9IDYpCiAgICAKICAgIFByZWRpY3Rvck1vZGVsID0gdHJhaW4oSHVudGVycyB+IC4sIGRhdGEgPSBzZWxlY3QodHJhaW5kYXRhLC1pcHJlZGljdG9yKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9IGltZXRob2QsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB0dW5lTGVuZ3RoID0gMTUsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSBmaXRDb250cm9sKQogICAgCiAgICBwcmludChQcmVkaWN0b3JNb2RlbCkKICAgIAogICAgIyBjaGVjayBwZXJmb3JtYW5jZQogICAgcHJlZGljdGRhdGEgPC0gcHJlZGljdChQcmVkaWN0b3JNb2RlbCwgdGVzdGRhdGEpCiAgICAKICAgIFByZWRpY3RvcnBlcmZvcm1hbmNlJG1ldGhvZCA8LSBpbWV0aG9kCiAgICBQcmVkaWN0b3JwZXJmb3JtYW5jZSRtaXNzaW5nX3ByZWRpY3RvciA8LSBpcHJlZGljdG9yCiAgICBQcmVkaWN0b3JwZXJmb3JtYW5jZSRSTVNFIDwtIHBvc3RSZXNhbXBsZShwcmVkID0gcHJlZGljdGRhdGEsIG9icyA9IHRlc3RkYXRhJEh1bnRlcnMpWzFdCiAgICBQcmVkaWN0b3JwZXJmb3JtYW5jZSA8LSBhcy5kYXRhLmZyYW1lKFByZWRpY3RvcnBlcmZvcm1hbmNlKQogICAgUHJlZGljdG9ycGVyZm9ybWFuY2VfYWxsIDwtIHJiaW5kKFByZWRpY3RvcnBlcmZvcm1hbmNlX2FsbCxQcmVkaWN0b3JwZXJmb3JtYW5jZSkKICB9Cn0KYGBgCgpgYGB7cn0KUHJlZGljdG9ycGVyZm9ybWFuY2VfYWxsCmBgYAoKCnN2TVJhZGlhbCB3aWxsIHBlcmZvcm0gYmV0dGVyIHdpdGggYWxsIG9mIHRoZSBwcmVkaWN0b3JzLCB3aGlsZSBra25uIHBlcmZvcm1zCmJldHRlciB3aXRoIG9ubHkgVW5pdCBhbmQgWWVhciBmaWVsZHMKClVzZSBhYm92ZSBpbmZvcm1hdGlvbiB0byB0ZXN0IG91dCB2YXJpb3VzIGNvbWJpbmF0aW9ucyBvZiBwcmVwcm9jZXNzaW5nIGFuZCBwcmVkaWN0b3Igc2V0cwoKCmtrbm4gd2l0aG91dCBRdW90YSBhbmQgRHJhd24KYGBge3J9CmZpdENvbnRyb2wgPC0gdHJhaW5Db250cm9sKAogIG1ldGhvZCA9ICJhZGFwdGl2ZV9jdiIsICNyZXBlYXRlZGN2LCAKICBzZWFyY2ggPSAncmFuZG9tJywKICBudW1iZXIgPSAxMCwgIzQKICByZXBlYXRzID0gMTAsICMxMAogICMgY2xhc3NQcm9icyA9IFRSVUUsCiAgIyBzYXZlUHJlZCA9IFRSVUUsCiAgYWxsb3dQYXJhbGxlbCA9IFRSVUUsCiAgc3VtbWFyeUZ1bmN0aW9uID0gZGVmYXVsdFN1bW1hcnkpCgpyZWdpc3RlckRvU0VRKCkKcmVnaXN0ZXJEb01DKGNvcmVzID0gNikKCmtrbm5Nb2RlbCA9IHRyYWluKEh1bnRlcnMgfiAuLCBkYXRhID0gc2VsZWN0KENPRWxrSHVudGVycywtUXVvdGEsIC1EcmF3biksCiAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJra25uIiwKICAgICAgICAgICAgICAgICAgdHVuZUxlbmd0aCA9IDc1LAogICAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSBmaXRDb250cm9sKQoKYGBgCmBgYHtyfQpra25uTW9kZWwKYGBgCgoKQmVzdCBSTVNFLCBub3Qgc3VyZSB3aHkgY2FyZXQgaXMgc2VsZWN0aW5nIHBhcmFtZXRlcnMgd2l0aCBoaWdoZXIgUk1TRSwgbGV0cyBzZWxlY3QgbWFudWFsbHkKYGBge3J9ClJTTUVra25uIDwtIGZpbHRlcihra25uTW9kZWwkcmVzdWx0cywgUk1TRSA9PSBtaW4oUk1TRSkpClJTTUVra25uJGtlcm5lbCA8LSBhcy5jaGFyYWN0ZXIoUlNNRWtrbm4ka2VybmVsKQpgYGAKCnJ1biBhZ2FpbiB3aXRoIGEgdHVuZSBncmlkCmBgYHtyfQpra25uVHVuZUdyaWQgPC0gZGF0YS5mcmFtZShrbWF4ID0gYyhSU01Fa2tubiRrbWF4LFJTTUVra25uJGttYXgsUlNNRWtrbm4ka21heCxSU01Fa2tubiRrbWF4LFJTTUVra25uJGttYXgpLAogICAgICAgICAgICAgICAgICAgICAgICAgICBkaXN0YW5jZSA9IGMoUlNNRWtrbm4kZGlzdGFuY2UqLjcsUlNNRWtrbm4kZGlzdGFuY2UqLjksUlNNRWtrbm4kZGlzdGFuY2UsUlNNRWtrbm4kZGlzdGFuY2UqMS4xLFJTTUVra25uJGRpc3RhbmNlKjEuMyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGtlcm5lbCA9IGMoUlNNRWtrbm4ka2VybmVsLFJTTUVra25uJGtlcm5lbCxSU01Fa2tubiRrZXJuZWwsUlNNRWtrbm4ka2VybmVsLFJTTUVra25uJGtlcm5lbCkpCgpmaXRDb250cm9sIDwtIHRyYWluQ29udHJvbCgKICBtZXRob2QgPSAicmVwZWF0ZWRjdiIsICNyZXBlYXRlZGN2LCAKICBudW1iZXIgPSAxMCwgIzQKICByZXBlYXRzID0gMTAsICMxMAogIGFsbG93UGFyYWxsZWwgPSBUUlVFLAogIHN1bW1hcnlGdW5jdGlvbiA9IGRlZmF1bHRTdW1tYXJ5KQoKcmVnaXN0ZXJEb1NFUSgpCnJlZ2lzdGVyRG9NQyhjb3JlcyA9IDYpCgpra25uR3JpZE1vZGVsID0gdHJhaW4oSHVudGVycyB+IC4sIGRhdGEgPSBzZWxlY3QoQ09FbGtIdW50ZXJzLC1RdW90YSwgLURyYXduKSwKICAgICAgICAgICAgICAgICAgbWV0aG9kID0gImtrbm4iLAogICAgICAgICAgICAgICAgICB0dW5lR3JpZCA9IGtrbm5UdW5lR3JpZCwKICAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gZml0Q29udHJvbCkKYGBgCmBgYHtyfQpra25uR3JpZE1vZGVsCmBgYAoKCkJlc3QgUk1TRSwgbm90IHN1cmUgd2h5IGNhcmV0IGlzIHNlbGVjdGluZyBwYXJhbWV0ZXJzIHdpdGggaGlnaGVyIFJNU0UsIGxldHMgc2VsZWN0IG1hbnVhbGx5CmBgYHtyfQpSU01Fa2tubiA8LSBmaWx0ZXIoa2tubkdyaWRNb2RlbCRyZXN1bHRzLCBSTVNFID09IG1pbihSTVNFKSkKUlNNRWtrbm4ka2VybmVsIDwtIGFzLmNoYXJhY3RlcihSU01Fa2tubiRrZXJuZWwpCmBgYAoKcnVuIGFnYWluIHdpdGggYSB0dW5lIGdyaWQKYGBge3J9Cmtrbm5UdW5lR3JpZDIgPC0gZGF0YS5mcmFtZShrbWF4ID0gYyhSU01Fa2tubiRrbWF4Ki43LFJTTUVra25uJGttYXgqLjksUlNNRWtrbm4ka21heCxSU01Fa2tubiRrbWF4KjEuMSxSU01Fa2tubiRrbWF4KjEuMyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGRpc3RhbmNlID0gYyhSU01Fa2tubiRkaXN0YW5jZSxSU01Fa2tubiRkaXN0YW5jZSxSU01Fa2tubiRkaXN0YW5jZSxSU01Fa2tubiRkaXN0YW5jZSxSU01Fa2tubiRkaXN0YW5jZSksCiAgICAgICAgICAgICAgICAgICAgICAgICAgIGtlcm5lbCA9IGMoUlNNRWtrbm4ka2VybmVsLFJTTUVra25uJGtlcm5lbCxSU01Fa2tubiRrZXJuZWwsUlNNRWtrbm4ka2VybmVsLFJTTUVra25uJGtlcm5lbCkpCgpyZWdpc3RlckRvU0VRKCkKcmVnaXN0ZXJEb01DKGNvcmVzID0gNikKCmtrbm5HcmlkTW9kZWwyID0gdHJhaW4oSHVudGVycyB+IC4sIGRhdGEgPSBzZWxlY3QoQ09FbGtIdW50ZXJzLC1RdW90YSwgLURyYXduKSwKICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJra25uIiwKICAgICAgICAgICAgICAgICAgICAgIHR1bmVHcmlkID0ga2tublR1bmVHcmlkMiwKICAgICAgICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IGZpdENvbnRyb2wpCgpgYGAKCmBgYHtyfQpra25uR3JpZE1vZGVsMgpgYGAKCgpPbmUgbW9yZSB0aW1lIG9uIGZpbmFsIHBhcmFtZXRlciAoa2VybmVsKQpCZXN0IFJNU0UsIG5vdCBzdXJlIHdoeSBjYXJldCBpcyBzZWxlY3RpbmcgcGFyYW1ldGVycyB3aXRoIGhpZ2hlciBSTVNFLCBsZXRzIHNlbGVjdCBtYW51YWxseQpgYGB7cn0KUlNNRWtrbm4gPC0gZmlsdGVyKGtrbm5HcmlkTW9kZWwyJHJlc3VsdHMsIFJNU0UgPT0gbWluKFJNU0UpKVsxLF0Ka2VybmVscyA8LSBsZXZlbHMoa2tubk1vZGVsJHJlc3VsdHMka2VybmVsKQpgYGAKCnJ1biBhZ2FpbiB3aXRoIGEgdHVuZSBncmlkCmBgYHtyfQpra25uVHVuZUdyaWQzIDwtIGRhdGEuZnJhbWUoa21heCA9IHJlcCg0NjUuMCw4KSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRpc3RhbmNlID0gcmVwKDAuMTM5NTU4Niw4KSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGtlcm5lbCA9IGtlcm5lbHMpCgpyZWdpc3RlckRvU0VRKCkKcmVnaXN0ZXJEb01DKGNvcmVzID0gNikKCmtrbm5HcmlkTW9kZWwzID0gdHJhaW4oSHVudGVycyB+IC4sIGRhdGEgPSBzZWxlY3QoQ09FbGtIdW50ZXJzLC1RdW90YSwgLURyYXduKSwKICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAia2tubiIsCiAgICAgICAgICAgICAgICAgICAgICAgdHVuZUdyaWQgPSBra25uVHVuZUdyaWQzLAogICAgICAgICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IGZpdENvbnRyb2wpCgpgYGAKCmBgYHtyfQpra25uR3JpZE1vZGVsMwpgYGAKCmBgYHtyfQpSU01Fa2tubiA8LSBmaWx0ZXIoa2tubkdyaWRNb2RlbDMkcmVzdWx0cywgUk1TRSA9PSBtaW4oUk1TRSkpCmBgYAoKQmVzdCBSTVNFIGZvciBra25uIHRodXMgZmFyCmBgYHtyfQpSU01Fa2tubiA8LSBmaWx0ZXIoa2tubk1vZGVsJHJlc3VsdHMsIFJNU0UgPT0gbWluKFJNU0UpKQpgYGAKCldvcmsgdGhydSBzb21lIHJlc2FtcGxpbmcgbWV0aG9kcyB3aXRoIGJlc3Qga2tubiBwYXJhbXMKYGBge3J9Cmtrbm5UdW5lR3JpZDQgPC0gZGF0YS5mcmFtZShrbWF4ID0gUlNNRWtrbm4ka21heCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGRpc3RhbmNlID0gUlNNRWtrbm4kZGlzdGFuY2UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICBrZXJuZWwgPSBhcy5jaGFyYWN0ZXIoUlNNRWtrbm4ka2VybmVsKSkKCnRyYWlubWV0aG9kcyA8LSBjKCJib290IiwgImJvb3Q2MzIiLCAib3B0aW1pc21fYm9vdCIsICJib290X2FsbCIsICJjdiIsICJyZXBlYXRlZGN2IiwgIkxPT0NWIiwgIkxHT0NWIiwgIm5vbmUiKQp0cmFpbm1ldGhvZHBlcmZvcm1hbmNlX2FsbCA8LSBOVUxMCmZvciAoaXRyYWlubWV0aG9kIGluIHRyYWlubWV0aG9kcykgewogIHRyYWlubWV0aG9kcGVyZm9ybWFuY2UgPC0gTlVMTAogIGZpdENvbnRyb2wgPC0gdHJhaW5Db250cm9sKAogICAgbWV0aG9kID0gaXRyYWlubWV0aG9kLAogICAgbnVtYmVyID0gMTAsICM0CiAgICByZXBlYXRzID0gMTAsICMxMAogICAgYWxsb3dQYXJhbGxlbCA9IFRSVUUsCiAgICBzdW1tYXJ5RnVuY3Rpb24gPSBkZWZhdWx0U3VtbWFyeSkKICAKICByZWdpc3RlckRvU0VRKCkKICByZWdpc3RlckRvTUMoY29yZXMgPSA2KQogIAogIGtrbm5UcmFpbk1vZGVsID0gdHJhaW4oSHVudGVycyB+IC4sIGRhdGEgPSBzZWxlY3QoQ09FbGtIdW50ZXJzLC1RdW90YSwgLURyYXduKSwKICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJra25uIiwKICAgICAgICAgICAgICAgICAgICAgICAgIHR1bmVHcmlkID0ga2tublR1bmVHcmlkNCwKICAgICAgICAgICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IGZpdENvbnRyb2wpCiAgCiAgcHJpbnQoa2tublRyYWluTW9kZWwpCiAgdHJhaW5tZXRob2RwZXJmb3JtYW5jZSA8LSBmaWx0ZXIoa2tublRyYWluTW9kZWwkcmVzdWx0cywgUk1TRSA9PSBtaW4oUk1TRSkpCiAgdHJhaW5tZXRob2RwZXJmb3JtYW5jZSR0cmFpbm1ldGhvZCA8LSBpdHJhaW5tZXRob2QKICB0cmFpbm1ldGhvZHBlcmZvcm1hbmNlX2FsbCA8LSByYmluZC5maWxsKHRyYWlubWV0aG9kcGVyZm9ybWFuY2VfYWxsLHRyYWlubWV0aG9kcGVyZm9ybWFuY2UpCn0KYGBgCmBgYHtyfQp0cmFpbm1ldGhvZHBlcmZvcm1hbmNlX2FsbApgYGAKCgpgYGB7cn0KZml0Q29udHJvbCA8LSB0cmFpbkNvbnRyb2woCiAgbWV0aG9kID0gIm9wdGltaXNtX2Jvb3QiLAogIG51bWJlciA9IDEwLCAjNAogIGFsbG93UGFyYWxsZWwgPSBUUlVFLAogIHN1bW1hcnlGdW5jdGlvbiA9IGRlZmF1bHRTdW1tYXJ5KQoKa2tubkZpbmFsVHJhaW5Nb2RlbCA9IHRyYWluKEh1bnRlcnMgfiAuLCBkYXRhID0gQ09FbGtIdW50ZXJzLAogICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJra25uIiwKICAgICAgICAgICAgICAgICAgICAgICB0dW5lR3JpZCA9IGtrbm5UdW5lR3JpZDQsCiAgICAgICAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gZml0Q29udHJvbCkKCmBgYAoKc2F2ZSBvZmYgZm9yIGZ1dHVyZSBsb2FkaW5nCmBgYHtyfQpzYXZlKGtrbm5GaW5hbFRyYWluTW9kZWwsIGZpbGUgPSAifi9fY29kZS9jb2xvcmFkby1kb3cvZGF0YXNldHMva2tubkZpbmFsVHJhaW5Nb2RlbC5SRGF0YSIpCmBgYAoKYmFjayB0byB0cmFpbiB2cyB0ZXN0IGRhdGEgZm9yIG9uZSBtb3JlIHBlcmZvcm1hbmNlIG1lYXN1cmUgYW5kIGNoYXJ0Li4uIGV2ZW4gdGhvdWdoCmZvciBmdXR1cmUgZGF0YSB3ZSB3aWxsIHVzZSB0aGUgZmluYWwgdHJhaW5lZCBtb2RlbApgYGB7cn0Ka2tublRyYWluTW9kZWwgPSB0cmFpbihIdW50ZXJzIH4gLiwgZGF0YSA9IHRyYWluZGF0YSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJra25uIiwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHR1bmVHcmlkID0ga2tublR1bmVHcmlkNCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IGZpdENvbnRyb2wpCmBgYAoKY2hlY2sgcGVyZm9ybWFuY2UKYGBge3J9CnByZWRpY3RkYXRhIDwtIHByZWRpY3Qoa2tublRyYWluTW9kZWwsIHRlc3RkYXRhKQoKcG9zdFJlc2FtcGxlKHByZWQgPSBwcmVkaWN0ZGF0YSwgb2JzID0gdGVzdGRhdGEkSHVudGVycykKYGBgCgpDaGFydCBwZXJmb3JtYW5jZSBvZiBwcmVkaWN0ZWQKYGBge3J9CmNoYXJ0cGVyZm9ybWFuY2UgPC0gZGF0YS5mcmFtZShwcmVkaWN0ZWQgPSBwcmVkaWN0ZGF0YSwgb2JzZXJ2ZWQgPSB0ZXN0ZGF0YSRIdW50ZXJzKQpgYGAKCmBgYHtyIGZpZy53aWR0aD0xMH0KZ2dwbG90KGNoYXJ0cGVyZm9ybWFuY2UsIGFlcyhwcmVkaWN0ZWQsb2JzZXJ2ZWQpKSArCiAgZ2VvbV9wb2ludCgpICsKICBsYWJzKHRpdGxlPSJQZXJmb3JtYW5jZSBvZiBOdW1iZXIgb2YgSHVudGVycyBQcmVkaWN0aW9uIiwgY2FwdGlvbj0ic291cmNlOiBjcHcuc3RhdGUuY28udXMiKQpgYGAKCgoKIyMgU1ZNIApPdXRwdXQgZnJvbSBBenVyZU1MCltNb2R1bGVPdXRwdXRdIFN1cHBvcnQgVmVjdG9yIE1hY2hpbmVzIHdpdGggUmFkaWFsIEJhc2lzIEZ1bmN0aW9uIEtlcm5lbCAKW01vZHVsZU91dHB1dF0gCltNb2R1bGVPdXRwdXRdIDE1NDAgc2FtcGxlcwpbTW9kdWxlT3V0cHV0XSAgICA0IHByZWRpY3RvcnMKW01vZHVsZU91dHB1dF0gCltNb2R1bGVPdXRwdXRdIE5vIHByZS1wcm9jZXNzaW5nCltNb2R1bGVPdXRwdXRdIFJlc2FtcGxpbmc6IENyb3NzLVZhbGlkYXRlZCAoMTAgZm9sZCwgcmVwZWF0ZWQgMTAgdGltZXMpIApbTW9kdWxlT3V0cHV0XSAKW01vZHVsZU91dHB1dF0gU3VtbWFyeSBvZiBzYW1wbGUgc2l6ZXM6IDEzODYsIDEzODYsIDEzODcsIDEzODcsIDEzODUsIDEzODUsIC4uLiAKW01vZHVsZU91dHB1dF0gCltNb2R1bGVPdXRwdXRdIFJlc2FtcGxpbmcgcmVzdWx0cyBhY3Jvc3MgdHVuaW5nIHBhcmFtZXRlcnM6CltNb2R1bGVPdXRwdXRdIApbTW9kdWxlT3V0cHV0XSAgIEMgICAgICAgIFJNU0UgIFJzcXVhcmVkICBSTVNFIFNEICBSc3F1YXJlZCBTRApbTW9kdWxlT3V0cHV0XSAgIDAuMjUgICAgIDI3NiAgIDAuOTQ2ICAgICAzMC42ICAgICAwLjAwODIyICAgIApbTW9kdWxlT3V0cHV0XSAgIDAuNSAgICAgIDIwMyAgIDAuOTYgICAgICAyMS40ICAgICAwLjAwNzU0ICAgIApbTW9kdWxlT3V0cHV0XSAgIDEgICAgICAgIDE4MCAgIDAuOTY1ICAgICAxNy43ICAgICAwLjAwNjcxICAgIApbTW9kdWxlT3V0cHV0XSAgIDIgICAgICAgIDE2OCAgIDAuOTY5ICAgICAxNS43ICAgICAwLjAwNjE0ICAgIApbTW9kdWxlT3V0cHV0XSAgIDQgICAgICAgIDE1OCAgIDAuOTcyICAgICAxNC45ICAgICAwLjAwNTUgICAgIApbTW9kdWxlT3V0cHV0XSAgIDggICAgICAgIDE1MCAgIDAuOTc1ICAgICAxNC43ICAgICAwLjAwNTA2ICAgIApbTW9kdWxlT3V0cHV0XSAgIDE2ICAgICAgIDE0NiAgIDAuOTc2ICAgICAxNC43ICAgICAwLjAwNDgxICAgIApbTW9kdWxlT3V0cHV0XSAgIDMyICAgICAgIDE0NCAgIDAuOTc2ICAgICAxNC43ICAgICAwLjAwNDc3ICAgIApbTW9kdWxlT3V0cHV0XSAgIDY0ICAgICAgIDE0MyAgIDAuOTc3ICAgICAxNC42ICAgICAwLjAwNDc0ICAgIApbTW9kdWxlT3V0cHV0XSAgIDEyOCAgICAgIDE0MCAgIDAuOTc3ICAgICAxNC4zICAgICAwLjAwNDY5ICAgIApbTW9kdWxlT3V0cHV0XSAgIDI1NiAgICAgIDEzOSAgIDAuOTc4ICAgICAxNSAgICAgICAwLjAwNDg1ICAgIApbTW9kdWxlT3V0cHV0XSAgIDUxMiAgICAgIDEzNyAgIDAuOTc4ICAgICAxNC45ICAgICAwLjAwNDg0ICAgIApbTW9kdWxlT3V0cHV0XSAgIDEwMjAgICAgIDEzNiAgIDAuOTc5ICAgICAxNS4zICAgICAwLjAwNDk0ICAgIApbTW9kdWxlT3V0cHV0XSAgIDIwNTAgICAgIDEzNSAgIDAuOTc5ICAgICAxNS42ICAgICAwLjAwNTEzICAgIApbTW9kdWxlT3V0cHV0XSAgIDQxMDAgICAgIDEzNiAgIDAuOTc5ICAgICAxNS41ICAgICAwLjAwNTEgICAgIApbTW9kdWxlT3V0cHV0XSAgIDgxOTAgICAgIDEzNyAgIDAuOTc4ICAgICAxNS43ICAgICAwLjAwNTE4ICAgIApbTW9kdWxlT3V0cHV0XSAgIDE2NDAwICAgIDEzOSAgIDAuOTc4ICAgICAxNi41ICAgICAwLjAwNTUxICAgIApbTW9kdWxlT3V0cHV0XSAgIDMyODAwICAgIDE0MSAgIDAuOTc3ICAgICAxNy42ICAgICAwLjAwNiAgICAgIApbTW9kdWxlT3V0cHV0XSAgIDY1NTAwICAgIDE0NSAgIDAuOTc2ICAgICAxOS40ICAgICAwLjAwNjU5ICAgIApbTW9kdWxlT3V0cHV0XSAgIDEzMTAwMCAgIDE1MSAgIDAuOTc0ICAgICAyMC44ICAgICAwLjAwNzE4ICAgIApbTW9kdWxlT3V0cHV0XSAgIDI2MjAwMCAgIDE2MSAgIDAuOTcgICAgICAyNy4yICAgICAwLjAxMDQgICAgIApbTW9kdWxlT3V0cHV0XSAgIDUyNDAwMCAgIDQ3OCAgIDAuOCAgICAgICAzMjUgICAgICAwLjE3MiAgICAgIApbTW9kdWxlT3V0cHV0XSAgIDEwNTAwMDAgIDExODAgIDAuNSAgICAgICAxMDEwICAgICAwLjIwNCAgICAgIApbTW9kdWxlT3V0cHV0XSAgIDIxMDAwMDAgIDMyNDAgIDAuMTQ4ICAgICAyMjYwICAgICAwLjExNyAgICAgIApbTW9kdWxlT3V0cHV0XSAgIDQxOTAwMDAgIDYwMDAgIDAuMDYwNCAgICA2NDAwICAgICAwLjA1MjcgICAgIApbTW9kdWxlT3V0cHV0XSAKW01vZHVsZU91dHB1dF0gVHVuaW5nIHBhcmFtZXRlciAnc2lnbWEnIHdhcyBoZWxkIGNvbnN0YW50IGF0IGEgdmFsdWUgb2YgMC4wMDM3NjUzCltNb2R1bGVPdXRwdXRdIFJNU0Ugd2FzIHVzZWQgdG8gc2VsZWN0IHRoZSBvcHRpbWFsIG1vZGVsIHVzaW5nICB0aGUgc21hbGxlc3QgdmFsdWUuCltNb2R1bGVPdXRwdXRdIFRoZSBmaW5hbCB2YWx1ZXMgdXNlZCBmb3IgdGhlIG1vZGVsIHdlcmUgc2lnbWEgPSAwLjAwMzc3IGFuZCBDID0gMjA0OC4gCgoKIyBydW4gYWdhaW4gd2l0aCBhIHR1bmUgZ3JpZApgYGB7cn0Kc3ZtUmFkVHVuZUdyaWQgPC0gZGF0YS5mcmFtZSguc2lnbWEgPSBjKDAuMDAzNzY1MywwLjAwMzc2NTMsMC4wMDM3NjUzLDAuMDAzNzY1MywwLjAwMzc2NTMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgLkMgPSBjKDIwNDgqLjcsMjA0OCouOSwyMDQ4LDIwNDgqMS4xLDIwNDgqMS4zKSkKCmZpdENvbnRyb2wgPC0gdHJhaW5Db250cm9sKAogIG1ldGhvZCA9ICJyZXBlYXRlZGN2IiwgI3JlcGVhdGVkY3YsIAogIG51bWJlciA9IDEwLCAjNAogIHJlcGVhdHMgPSAxMCwgIzEwCiAgYWxsb3dQYXJhbGxlbCA9IFRSVUUsCiAgc3VtbWFyeUZ1bmN0aW9uID0gZGVmYXVsdFN1bW1hcnkpCgpyZWdpc3RlckRvU0VRKCkKcmVnaXN0ZXJEb01DKGNvcmVzID0gNikKCnN2bVJhZEdyaWRNb2RlbCA9IHRyYWluKEh1bnRlcnMgfiAuLCBkYXRhID0gQ09FbGtIdW50ZXJzLAogICAgICAgICAgICAgICAgICAgICAgbWV0aG9kID0gInN2bVJhZGlhbCIsCiAgICAgICAgICAgICAgICAgICAgICB0dW5lR3JpZCA9IHN2bVJhZFR1bmVHcmlkLAogICAgICAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gZml0Q29udHJvbCkKYGBgCgpgYGB7cn0Kc3ZtUmFkR3JpZE1vZGVsCmBgYAoKCkJlc3QgUk1TRSwgbm90IHN1cmUgd2h5IGNhcmV0IGlzIHNlbGVjdGluZyBwYXJhbWV0ZXJzIHdpdGggaGlnaGVyIFJNU0UsIGxldHMgc2VsZWN0IG1hbnVhbGx5CmBgYHtyfQpSU01Fc3ZtUmFkIDwtIGZpbHRlcihzdm1SYWRHcmlkTW9kZWwkcmVzdWx0cywgUk1TRSA9PSBtaW4oUk1TRSkpCmBgYAoKcnVuIGFnYWluIHdpdGggYSB0dW5lIGdyaWQKYGBge3J9CnN2bVJhZFR1bmVHcmlkMiA8LSBkYXRhLmZyYW1lKC5zaWdtYSA9IGMoUlNNRXN2bVJhZCRzaWdtYSouNyxSU01Fc3ZtUmFkJHNpZ21hKi45LFJTTUVzdm1SYWQkc2lnbWEsUlNNRXN2bVJhZCRzaWdtYSoxLjEsUlNNRXN2bVJhZCRzaWdtYSoxLjMpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIC5DID0gYyhSU01Fc3ZtUmFkJEMsUlNNRXN2bVJhZCRDLFJTTUVzdm1SYWQkQyxSU01Fc3ZtUmFkJEMsUlNNRXN2bVJhZCRDKSkKCnJlZ2lzdGVyRG9TRVEoKQpyZWdpc3RlckRvTUMoY29yZXMgPSA2KQoKc3ZtUmFkR3JpZE1vZGVsMiA9IHRyYWluKEh1bnRlcnMgfiAuLCBkYXRhID0gQ09FbGtIdW50ZXJzLAogICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAic3ZtUmFkaWFsIiwKICAgICAgICAgICAgICAgICAgICAgICAgdHVuZUdyaWQgPSBzdm1SYWRUdW5lR3JpZDIsCiAgICAgICAgICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IGZpdENvbnRyb2wpCmBgYAoKYGBge3J9CnN2bVJhZEdyaWRNb2RlbDIKYGBgCgpgYGB7cn0KUlNNRXN2bVJhZCA8LSBmaWx0ZXIoc3ZtUmFkR3JpZE1vZGVsMiRyZXN1bHRzLCBSTVNFID09IG1pbihSTVNFKSkKYGBgCgpXb3JrIHRocnUgc29tZSByZXNhbXBsaW5nIG1ldGhvZHMgd2l0aCBiZXN0IGtrbm4gcGFyYW1zCmBgYHtyfQpzdm1SYWRUdW5lR3JpZDMgPC0gZGF0YS5mcmFtZSguc2lnbWEgPSBSU01Fc3ZtUmFkJHNpZ21hLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgLkMgPSBSU01Fc3ZtUmFkJEMpCgp0cmFpbm1ldGhvZHMgPC0gYygiYm9vdCIsICJib290NjMyIiwgIm9wdGltaXNtX2Jvb3QiLCAiY3YiLCAicmVwZWF0ZWRjdiIsICJMT09DViIsICJMR09DViIsICJub25lIikKdHJhaW5tZXRob2RwZXJmb3JtYW5jZV9hbGwgPC0gTlVMTApmb3IgKGl0cmFpbm1ldGhvZCBpbiB0cmFpbm1ldGhvZHMpIHsKICB0cmFpbm1ldGhvZHBlcmZvcm1hbmNlIDwtIE5VTEwKICBmaXRDb250cm9sIDwtIHRyYWluQ29udHJvbCgKICAgIG1ldGhvZCA9IGl0cmFpbm1ldGhvZCwKICAgIG51bWJlciA9IDEwLCAjNAogICAgcmVwZWF0cyA9IDEwLCAjMTAKICAgIGFsbG93UGFyYWxsZWwgPSBUUlVFLAogICAgc3VtbWFyeUZ1bmN0aW9uID0gZGVmYXVsdFN1bW1hcnkpCiAgCiAgcmVnaXN0ZXJEb1NFUSgpCiAgcmVnaXN0ZXJEb01DKGNvcmVzID0gNikKICAKICBzdm1SYWRUcmFpbk1vZGVsID0gdHJhaW4oSHVudGVycyB+IC4sIGRhdGEgPSBDT0Vsa0h1bnRlcnMsCiAgICAgICAgICAgICAgICAgICAgICAgICBtZXRob2QgPSAic3ZtUmFkaWFsIiwKICAgICAgICAgICAgICAgICAgICAgICAgIHR1bmVHcmlkID0gc3ZtUmFkVHVuZUdyaWQzLAogICAgICAgICAgICAgICAgICAgICAgICAgdHJDb250cm9sID0gZml0Q29udHJvbCkKICAKICBwcmludChzdm1SYWRUcmFpbk1vZGVsKQogIHRyYWlubWV0aG9kcGVyZm9ybWFuY2UgPC0gZmlsdGVyKHN2bVJhZFRyYWluTW9kZWwkcmVzdWx0cywgUk1TRSA9PSBtaW4oUk1TRSkpCiAgdHJhaW5tZXRob2RwZXJmb3JtYW5jZSR0cmFpbm1ldGhvZCA8LSBpdHJhaW5tZXRob2QKICB0cmFpbm1ldGhvZHBlcmZvcm1hbmNlX2FsbCA8LSByYmluZC5maWxsKHRyYWlubWV0aG9kcGVyZm9ybWFuY2VfYWxsLHRyYWlubWV0aG9kcGVyZm9ybWFuY2UpCn0KYGBgCmBgYHtyfQp0cmFpbm1ldGhvZHBlcmZvcm1hbmNlX2FsbApgYGAKCmBgYHtyfQpmaXRDb250cm9sIDwtIHRyYWluQ29udHJvbCgKICBtZXRob2QgPSAib3B0aW1pc21fYm9vdCIsCiAgbnVtYmVyID0gMTAsICM0CiAgYWxsb3dQYXJhbGxlbCA9IFRSVUUsCiAgc3VtbWFyeUZ1bmN0aW9uID0gZGVmYXVsdFN1bW1hcnkpCgpzdm1SYWRGaW5hbFRyYWluTW9kZWwgPSB0cmFpbihIdW50ZXJzIH4gLiwgZGF0YSA9IENPRWxrSHVudGVycywKICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJzdm1SYWRpYWwiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgdHVuZUdyaWQgPSBzdm1SYWRUdW5lR3JpZDMsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ckNvbnRyb2wgPSBmaXRDb250cm9sKQoKYGBgCgpzYXZlIG9mZiBmb3IgZnV0dXJlIGxvYWRpbmcKYGBge3J9CnNhdmUoc3ZtUmFkRmluYWxUcmFpbk1vZGVsLCBmaWxlID0gIn4vX2NvZGUvY29sb3JhZG8tZG93L2RhdGFzZXRzL3N2bVJhZEZpbmFsVHJhaW5Nb2RlbC5SRGF0YSIpCmBgYAoKYmFjayB0byB0cmFpbiB2cyB0ZXN0IGRhdGEgZm9yIG9uZSBtb3JlIHBlcmZvcm1hbmNlIG1lYXN1cmUgYW5kIGNoYXJ0Li4uIGV2ZW4gdGhvdWdoCmZvciBmdXR1cmUgZGF0YSB3ZSB3aWxsIHVzZSB0aGUgZmluYWwgdHJhaW5lZCBtb2RlbApgYGB7cn0Kc3ZtUmFkVHJhaW5Nb2RlbCA9IHRyYWluKEh1bnRlcnMgfiAuLCBkYXRhID0gdHJhaW5kYXRhLAogICAgICAgICAgICAgICAgICAgICAgIG1ldGhvZCA9ICJzdm1SYWRpYWwiLAogICAgICAgICAgICAgICAgICAgICAgIHR1bmVHcmlkID0gc3ZtUmFkVHVuZUdyaWQzLAogICAgICAgICAgICAgICAgICAgICAgIHRyQ29udHJvbCA9IGZpdENvbnRyb2wpCmBgYAoKY2hlY2sgcGVyZm9ybWFuY2UKYGBge3J9CnByZWRpY3RkYXRhIDwtIHByZWRpY3Qoc3ZtUmFkVHJhaW5Nb2RlbCwgdGVzdGRhdGEpCnBvc3RSZXNhbXBsZShwcmVkID0gcHJlZGljdGRhdGEsIG9icyA9IHRlc3RkYXRhJEh1bnRlcnMpCmBgYAoKQ2hhcnQgcGVyZm9ybWFuY2Ugb2YgcHJlZGljdGVkCmBgYHtyIGZpZy53aWR0aD0xMH0KY2hhcnRwZXJmb3JtYW5jZSA8LSBkYXRhLmZyYW1lKHByZWRpY3RlZCA9IHByZWRpY3RkYXRhLCBvYnNlcnZlZCA9IHRlc3RkYXRhJEh1bnRlcnMpCmdncGxvdChjaGFydHBlcmZvcm1hbmNlLCBhZXMocHJlZGljdGVkLG9ic2VydmVkKSkgKwogIGdlb21fcG9pbnQoKSArCiAgbGFicyh0aXRsZT0iUGVyZm9ybWFuY2Ugb2YgTnVtYmVyIG9mIEh1bnRlcnMgUHJlZGljdGlvbiIsIGNhcHRpb249InNvdXJjZTogY3B3LnN0YXRlLmNvLnVzIikKYGBgCgoKa2tubiBwZXJmb3JtZWQgYmV0dGVyIHRoYW4gc3ZtUmFkaWFsIFJNU0U9MTMwIHZzIDE1NApgYGB7cn0KRmluYWxIdW50ZXJzbW9kZWwgPC0ga2tubkZpbmFsVHJhaW5Nb2RlbAojIEZpbmFsSHVudGVyc21vZGVsIDwtIHN2bVJhZEZpbmFsVHJhaW5Nb2RlbApgYGAKYGBge3J9CnNhdmUoRmluYWxIdW50ZXJzbW9kZWwsIGZpbGUgPSAifi9fY29kZS9jb2xvcmFkby1kb3cvZGF0YXNldHMvRmluYWxIdW50ZXJzbW9kZWwuUkRhdGEiKQpgYGAKClVzZSB0aGUgMjAxOCBEcmF3IGRhdGEgdG8gcHJlZGljdCB0aGUgbnVtYmVyIG9mIGh1bnRlcnMgaW4gMjAxOApgYGB7cn0KQ09FbGtEcmF3MjAxOCA8LSBmaWx0ZXIoQ09FbGtEcmF3LCBZZWFyID09IDIwMTgpCkNPRWxrSHVudGVyczIwMTggPC0gQ09FbGtEcmF3MjAxOFssIGNvbG5hbWVzKENPRWxrRHJhdzIwMTgpICVpbiUgYygiVW5pdCIsRmluYWxIdW50ZXJzbW9kZWwkY29lZm5hbWVzKV0KCkNPRWxrSHVudGVyczIwMTggPC0gYXMuZGF0YS5mcmFtZSh1bmlxdWUoQ09FbGtIdW50ZXJzJFVuaXQpKQpjb2xuYW1lcyhDT0Vsa0h1bnRlcnMyMDE4KSA8LSAiVW5pdCIKQ09FbGtIdW50ZXJzMjAxOCRZZWFyIDwtIDIwMTgKQ09FbGtIdW50ZXJzMjAxOCA8LSBsZWZ0X2pvaW4oQ09FbGtIdW50ZXJzMjAxOCxmaWx0ZXIoQ09FbGtEcmF3LFllYXI9PTIwMTgpKQojIFJlcGxhY2UgdGhlIGRyYXcgZGF0YSB0aGF0IGRvbid0IGhhdmUgZW50cmllcyB3aXRoIDAKQ09FbGtIdW50ZXJzMjAxOCREcmF3bltpcy5uYShDT0Vsa0h1bnRlcnMyMDE4JERyYXduKV0gPC0gMApDT0Vsa0h1bnRlcnMyMDE4JFF1b3RhW2lzLm5hKENPRWxrSHVudGVyczIwMTgkUXVvdGEpXSA8LSAwCgpDT0Vsa0h1bnRlcnMyMDE4IDwtIENPRWxrSHVudGVyczIwMThbLCBjb2xuYW1lcyhDT0Vsa0h1bnRlcnMyMDE4KSAlaW4lIGMoIlVuaXQiLEZpbmFsSHVudGVyc21vZGVsJGNvZWZuYW1lcyldCgpDT0Vsa0h1bnRlcnMyMDE4JEh1bnRlcnMgPC0gcm91bmQocHJlZGljdChGaW5hbEh1bnRlcnNtb2RlbCwgQ09FbGtIdW50ZXJzMjAxOCkpCgpDT0Vsa0h1bnRlcnMyMDE4JEh1bnRlcnNbQ09FbGtIdW50ZXJzMjAxOCRIdW50ZXJzPDBdIDwtIDAKYGBgCgpTYXZlIG9mZiBzbyB3ZSBkb24ndCBoYXZlIHRvIHJlY3JlYXRlIHRoZSBtb2RlbCBldmVyeXRpbWUgd2Ugd2FudCB0aGUgcmVzdWx0cwpgYGB7cn0Kc2F2ZShDT0Vsa0h1bnRlcnMyMDE4LGZpbGU9IkNPRWxrSHVudGVyczIwMTguUkRhdGEiKQpgYGAKCioqKgojIyBUb3RhbCBFbGsgSGFydmVzdAojIyMgU3RhdGV3aWRlCkdyb3VwIHNlYXNvbnMKYGBge3J9CkNPRWxrSHVudGVyc1N0YXRld2lkZSA8LSBzdW1tYXJpc2UoZ3JvdXBfYnkoQ09FbGtSaWZsZUFsbCxZZWFyLFVuaXQpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgSHVudGVycyA9IHN1bShjKEh1bnRlcnMuQW50bGVyZWQsSHVudGVycy5BbnRsZXJsZXNzLEh1bnRlcnMuRWl0aGVyKSxuYS5ybSA9IFQpKQpDT0Vsa0h1bnRlcnMyMDE4YiA8LSBDT0Vsa0h1bnRlcnMyMDE4CiMgQ09FbGtIdW50ZXJzMjAxOGIkWWVhciA8LSBhcy5jaGFyYWN0ZXIoQ09FbGtIdW50ZXJzMjAxOGIkWWVhcikKCiMgSm9pbiAyMDE4IHRvIGhpc3RvcmljIGRhdGEKQ09FbGtIdW50ZXJzQWxsIDwtIHJiaW5kLmZpbGwoQ09FbGtIdW50ZXJzU3RhdGV3aWRlLENPRWxrSHVudGVyczIwMThiKQoKIyBHcm91cCBVbml0cwpDT0Vsa0h1bnRlcnNTdGF0ZXdpZGUgPC0gc3VtbWFyaXNlKGdyb3VwX2J5KENPRWxrSHVudGVyc0FsbCxZZWFyKSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBIdW50ZXJzID0gc3VtKEh1bnRlcnMpKQpgYGAKCmBgYHtyIGZpZy53aWR0aD0xMH0KZ2dwbG90KENPRWxrSHVudGVyc1N0YXRld2lkZSwgYWVzKFllYXIsSHVudGVycykpICsKICBnZW9tX2JhcihzdGF0PSJpZGVudGl0eSIsZmlsbD1nZ3RoZW1lc19kYXRhJGhjJHBhbGV0dGVzJGRlZmF1bHRbMl0pICsKICBjb29yZF9jYXJ0ZXNpYW4oeWxpbSA9IGMoMTMwMDAwLDE1NTAwMCkpICsKICBsYWJzKHRpdGxlPSJTdGF0ZXdpZGUgRWxrIEh1bnRlcnMiLCBjYXB0aW9uPSJzb3VyY2U6IGNwdy5zdGF0ZS5jby51cyIpCmBgYAoKCj4gVE9ETyBjb21tZW50YXJ5CgoqKioKCiMjIyBIdW50ZXJzIGJ5IFVuaXQKSSdkIGxpa2UgdG8ga25vdyB3aGVyZSB0aGUgaHVudGVycyBhcmUgZGlzdHJpYnV0ZWQgYWNyb3NzIHRoZSBzdGF0ZS4KCk5leHQgeWVhcidzIGRhdGEKYGBge3J9ClllYXIyMDE4IDwtIGZpbHRlcihDT0Vsa0h1bnRlcnNBbGwsIFllYXIgPT0gIjIwMTgiKQpIdW50ZXJzdG9QbG90IDwtIGxlZnRfam9pbihVbml0Ym91bmRhcmllczIsWWVhcjIwMTgsIGJ5PWMoIlVuaXQiKSkKYGBgCmBgYHtyIGZpZy53aWR0aD0xMCwgZmlnLmhlaWdodD04LjQ2fQpnZ3Bsb3QoSHVudGVyc3RvUGxvdCwgYWVzKGxvbmcsIGxhdCwgZ3JvdXAgPSBncm91cCkpICsgCiAgZ2VvbV9wb2x5Z29uKGFlcyhmaWxsID0gSHVudGVycyksY29sb3VyID0gImdyZXk1MCIsIHNpemUgPSAuMikgKyAjVW5pdCBib3VuZGFyaWVzCiAgZ2VvbV9wYXRoKGRhdGEgPSBDT3JvYWRzLGFlcyh4ID0gbG9uZywgeSA9IGxhdCwgZ3JvdXAgPSBncm91cCksIGNvbG9yPSIjMzg3OEM3IixzaXplPTIpICsgI1JvYWRzCiAgZ2VvbV90ZXh0KGRhdGE9ZGF0YV9jZW50cm9pZHMsYWVzKHg9bG9uZ2l0dWRlLHk9bGF0aXR1ZGUsbGFiZWwgPSBVbml0KSxzaXplPTMpICsgI1VuaXQgbGFiZWxzCiAgc2NhbGVfZmlsbF9kaXN0aWxsZXIocGFsZXR0ZSA9ICJPcmFuZ2VzIixkaXJlY3Rpb24gPSAxLG5hLnZhbHVlID0gJ2xpZ2h0IGdyZXknKSArCiAgeGxhYigiIikgKyB5bGFiKCIiKSArCiAgbGFicyh0aXRsZT0iUHJlZGljdGVkIDIwMTggQ29sb3JhZG8gRWxrIEh1bnRlcnMiLCBjYXB0aW9uPSJzb3VyY2U6IGNwdy5zdGF0ZS5jby51cyIpCmBgYAoKCj4gVE9ETyAtIGNvbW1lbnRhcnkKCioqKgoKIyMjIFllYXIgdG8gWWVhciBIdW50ZXIgVHJlbmRzCkNyZWF0ZSBhIHBuZyBvZiBlYWNoIHllYXIKYGBge3J9Cmljb3VudGVyIDwtIDAKZm9yIChpbWFwIGluIHVuaXF1ZShDT0Vsa0h1bnRlcnNBbGwkWWVhcikpewogICMgQ29sb3JhZG8gYXNwZWN0IHJhdGlvID0gMTA4N3cgeCA4MDBoIC0+IDEuMzU4NzUKICAjIFVzZSB0cmlhbCBhbmQgZXJyb3IgdG8gZGV0ZXJtaW5lIHdoaWNoIHdpZHRoIGFuZCBoZWlnaHQgdG8gZGVmaW5lIGZvciBwbmcgZmlsZXMgdGhhdCB3aWxsIHJldGFpbiB0aGUgY29ycmVjdCBhc3BlY3QgcmF0aW8KICBwbmcoZmlsZT1wYXN0ZSgiSHVudGVyc01hcCIsaW1hcCwiLnBuZyIpLCB3aWR0aD05NDgsIGhlaWdodD03MDApCiAgeWVhcnBsb3QgPC0gZmlsdGVyKENPRWxrSHVudGVyc0FsbCwgWWVhciA9PSBpbWFwKQogIEh1bnRlcnN0b1Bsb3QgPC0gbGVmdF9qb2luKFVuaXRib3VuZGFyaWVzMix5ZWFycGxvdCwgYnk9YygiVW5pdCIpKQogIHAxIDwtIGdncGxvdChIdW50ZXJzdG9QbG90LCBhZXMobG9uZywgbGF0LCBncm91cCA9IGdyb3VwKSkgKyAKICAgIGdlb21fcG9seWdvbihhZXMoZmlsbCA9IEh1bnRlcnMpLGNvbG91ciA9ICJncmV5NTAiLCBzaXplID0gLjIpICsgI1VuaXQgYm91bmRhcmllcwogICAgZ2VvbV9wYXRoKGRhdGEgPSBDT3JvYWRzLGFlcyh4ID0gbG9uZywgeSA9IGxhdCwgZ3JvdXAgPSBncm91cCksIGNvbG9yPSIjMzg3OEM3IixzaXplPTIpICsgI1JvYWRzCiAgICBnZW9tX3RleHQoZGF0YT1kYXRhX2NlbnRyb2lkcyxhZXMoeD1sb25naXR1ZGUseT1sYXRpdHVkZSxsYWJlbCA9IFVuaXQpLHNpemU9NSkgKyAjVW5pdCBsYWJlbHMKICAgIHNjYWxlX2ZpbGxfZGlzdGlsbGVyKHBhbGV0dGUgPSAiT3JhbmdlcyIsCiAgICAgICAgICAgICAgICAgICAgICAgICBkaXJlY3Rpb24gPSAxLAogICAgICAgICAgICAgICAgICAgICAgICAgbmEudmFsdWUgPSAnbGlnaHQgZ3JleScsCiAgICAgICAgICAgICAgICAgICAgICAgICBsaW1pdHMgPSBjKDAsbWF4KENPRWxrSHVudGVyc0FsbCRIdW50ZXJzKSkpICsgI2ZpeCBzbyBlYWNoIHllYXIgY2hhcnQgaGFzIHNhbWUgY29sb3IgYnJlYWtzCiAgICB4bGFiKCIiKSArIHlsYWIoIiIpICsKICAgIHRoZW1lKHBsb3QudGl0bGU9ZWxlbWVudF90ZXh0KGhqdXN0ID0gLjUpKSArCiAgICB0aGVtZShwbG90LnN1YnRpdGxlPWVsZW1lbnRfdGV4dChoanVzdCA9IGljb3VudGVyL2xlbmd0aCh1bmlxdWUoQ09FbGtIdW50ZXJzQWxsJFllYXIpKSkpICsKICAgIGxhYnModGl0bGU9IkNvbG9yYWRvIEVsayBIdW50ZXJzIiwgc3VidGl0bGU9aW1hcCwgY2FwdGlvbj0ic291cmNlOiBjcHcuc3RhdGUuY28udXMiKQogIHBsb3QocDEpCiAgZGV2Lm9mZigpCiAgaWNvdW50ZXIgPC0gaWNvdW50ZXIgKyAxCn0KYGBgCkNvbnZlcnQgdGhlIC5wbmcgZmlsZXMgdG8gb25lIC5naWYgZmlsZSB1c2luZyBJbWFnZU1hZ2ljay4gCmBgYHtyfQpzeXN0ZW0oImNvbnZlcnQgLWRlbGF5IDE1MCAqLnBuZyBIdW50ZXJzbWFwUHJlZC5naWYiKQpgYGAKCiFbXShIdW50ZXJzbWFwUHJlZC5naWYpCj4gVE9ETyAtIGNvbW1lbnRhcnkKClJlbW92ZSB0aGUgLnBuZyBmaWxlcwpgYGB7cn0KZmlsZS5yZW1vdmUobGlzdC5maWxlcyhwYXR0ZXJuPSIucG5nIikpCmBgYAoKKioqCiMjIyBOdW1iZXIgb2YgSHVudGVycyBSYW5rIG9mIHRoZSBVbml0cwpXb3VsZCBhbHNvIGJlIGJlbmVmaWNpYWwgdG8gcmFuayBlYWNoIHVuaXQgc28gSSBjYW4gcmVmZXJlbmNlIGxhdGVyLiBJbiB0aGlzIGNhc2UKYXZlcmFnZSB0aGUgbnVtYmVyIG9mIGh1bnRlcnMgb2YgdGhlIGxhc3QgZmV3IHllYXJzCmBgYHtyfQpIdW50ZXJSYW5rMjAxOCA8LSBmaWx0ZXIoQ09FbGtIdW50ZXJzQWxsLCBhcy5udW1lcmljKFllYXIpID09IDIwMTgpCkh1bnRlclJhbmsyMDE4IDwtIHN1bW1hcmlzZShncm91cF9ieShIdW50ZXJSYW5rMjAxOCxVbml0KSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBIdW50ZXJzID0gbWVhbihIdW50ZXJzLG5hLnJtID0gVCkpCkh1bnRlclJhbmsyMDE4JEh1bnRlcnNSYW5rID0gcmFuaygtSHVudGVyUmFuazIwMTgkSHVudGVycykKCkh1bnRlclJhbmsyMDE4IDwtIGZpbHRlcihIdW50ZXJSYW5rMjAxOCwgSHVudGVyc1JhbmsgPD0gNTApICMgdG9wIDUwIHVuaXRzCiMgSW4gb3JkZXIgZm9yIHRoZSBjaGFydCB0byByZXRhaW4gdGhlIG9yZGVyIG9mIHRoZSByb3dzLCB0aGUgWCBheGlzIHZhcmlhYmxlIChpLmUuIHRoZSBjYXRlZ29yaWVzKSBoYXMgdG8gYmUgY29udmVydGVkIGludG8gYSBmYWN0b3IuCkh1bnRlclJhbmsyMDE4IDwtIEh1bnRlclJhbmsyMDE4W29yZGVyKC1IdW50ZXJSYW5rMjAxOCRIdW50ZXJzKSwgXSAgIyBzb3J0Ckh1bnRlclJhbmsyMDE4JFVuaXQgPC0gZmFjdG9yKEh1bnRlclJhbmsyMDE4JFVuaXQsIGxldmVscyA9IEh1bnRlclJhbmsyMDE4JFVuaXQpICAjIHRvIHJldGFpbiB0aGUgb3JkZXIgaW4gcGxvdC4KYGBgCgpMb2xsaXBvcCBDaGFydApgYGB7cn0KZ2dwbG90KEh1bnRlclJhbmsyMDE4LCBhZXMoeD1Vbml0LCB5PUh1bnRlcnMpKSArIAogIGdlb21fcG9pbnQoc2l6ZT0zKSArIAogIGdlb21fc2VnbWVudChhZXMoeD1Vbml0LCAKICAgICAgICAgICAgICAgICAgIHhlbmQ9VW5pdCwgCiAgICAgICAgICAgICAgICAgICB5PTAsIAogICAgICAgICAgICAgICAgICAgeWVuZD1IdW50ZXJzKSkgKyAKICBsYWJzKHRpdGxlPSJQcmVkaWN0ZWQgRWxrIEh1bnRlcnMgMjAxOFxuVG9wIDUwIFVuaXRzIiwgc3VidGl0bGU9Ikh1bnRlcnMgYnkgVW5pdCIsIGNhcHRpb249InNvdXJjZTogY3B3LnN0YXRlLmNvLnVzIikKYGBgCgo+IFRPRE8gLSBjb21tZW50YXJ5CgoqKioKIyMgQ29uY2x1c2lvbgo+IFRPRE8KCgo=